Java Tips | コレクション:シャッフル

Java Java
スポンサーリンク

シャッフルは「順番に意味を持たせないために、あえてぐちゃっと混ぜる」技

シャッフルは、ざっくり言うと
「コレクションの要素の順番をランダムに入れ替える」処理です。

テストデータの順番を毎回変えたい。
おすすめ表示の順番をランダムにしたい。
A/B テストでユーザーをランダムに振り分けたい。

こういう「順番に意味を持たせたくない」「偏りを減らしたい」場面で、
シャッフルはとても役に立ちます。


基本形:Collections.shuffle で List をシャッフルする

一番シンプルなシャッフル

Java には、標準でシャッフル用のユーティリティが用意されています。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ShuffleBasic {

    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));

        Collections.shuffle(numbers);

        System.out.println(numbers); // 例: [3, 1, 5, 2, 4]
    }
}
Java

ここでの重要ポイントは二つです。

一つ目は、「Collections.shuffle は List の“中身の順番”を直接書き換える(破壊的)」ということです。
元の numbers 自体がシャッフルされるので、「元の順番を残したい」場合はコピーを取る必要があります。

二つ目は、「List.of(...) で作った不変リストには、そのまま shuffle できない」ことです。
List.of の戻り値は変更不可なので、上の例のように new ArrayList<>(...) で可変リストにしてからシャッフルします。


元の順番を残したいときのシャッフル

コピーしてからシャッフルする

「元の順番も後で使いたいけど、ランダム順も欲しい」という場面はよくあります。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ShuffleCopySample {

    public static void main(String[] args) {
        List<String> original = List.of("A", "B", "C", "D");

        List<String> shuffled = new ArrayList<>(original);
        Collections.shuffle(shuffled);

        System.out.println(original); // [A, B, C, D]
        System.out.println(shuffled); // 例: [C, A, D, B]
    }
}
Java

ここでの重要ポイントは、
「シャッフルは“順番を変える”ので、元の順番を残したいなら必ずコピーを作る」という感覚です。

業務コードでは、「元データはそのまま」「表示用だけランダム順」といったケースが多いので、
new ArrayList<>(original) のような「コピーしてからシャッフル」が基本パターンになります。


シャッフルユーティリティを用意しておく

null や空 List を安全に扱う

毎回 new ArrayListCollections.shuffle を書くのは少し面倒なので、
ユーティリティにまとめてしまうとスッキリします。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public final class Shuffles {

    private Shuffles() {}

    public static <T> List<T> shuffledCopy(List<T> source) {
        if (source == null || source.isEmpty()) {
            return List.of();
        }
        List<T> copy = new ArrayList<>(source);
        Collections.shuffle(copy);
        return copy;
    }
}
Java

使い方はこうです。

List<String> names = List.of("山田", "佐藤", "鈴木");

List<String> randomOrder = Shuffles.shuffledCopy(names);
Java

ここでの重要ポイントは三つです。

一つ目は、「null や空 List のときに安全に空リストを返す」ことです。
呼び出し側は「とりあえずランダム順が欲しい」とだけ考えればよくなります。

二つ目は、「元の List は絶対に変更しない」という契約にしていることです。
shuffledCopy という名前からも、「コピーをシャッフルして返す」ことが伝わります。

三つ目は、「シャッフルのやり方(Collections.shuffle)を一箇所に閉じ込めている」ことです。
もし将来、別のランダム戦略に変えたくなっても、このユーティリティだけ直せば済みます。


Random を指定して「再現性のあるシャッフル」をする

テストや検証で「同じシャッフル結果が欲しい」場合

Collections.shuffle には、Random を渡せるオーバーロードもあります。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class ShuffleWithSeed {

    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));

        Random random = new Random(12345L); // シード固定

        Collections.shuffle(numbers, random);

        System.out.println(numbers); // 毎回同じ順番になる
    }
}
Java

ここでの重要ポイントは、
「Random のシードを固定すると、シャッフル結果が毎回同じになる」ということです。

これはテストや検証でとても役に立ちます。
「ランダムだけど、テストでは同じ結果が欲しい」というときに、
new Random(固定シード) を使うことで、
「再現性のあるランダム」を実現できます。

ユーティリティにするなら、こういう形もありです。

public static <T> List<T> shuffledCopy(List<T> source, long seed) {
    if (source == null || source.isEmpty()) {
        return List.of();
    }
    List<T> copy = new ArrayList<>(source);
    Collections.shuffle(copy, new Random(seed));
    return copy;
}
Java

シャッフルを業務でどう使うか

例1:おすすめ表示の順番をランダムにする

例えば、「おすすめ商品一覧」を毎回同じ順番で出すと、
上の方ばかりクリックされて偏りが出ます。

そこで、「候補一覧をシャッフルしてから、上位 N 件を表示する」というパターンがよく使われます。

List<Product> candidates = findRecommendedProducts(userId);

List<Product> randomized =
        Shuffles.shuffledCopy(candidates);

List<Product> top5 = randomized.stream()
                               .limit(5)
                               .toList();
Java

ここでの重要ポイントは、
「シャッフルは“順番のバイアスを減らすための道具”として使える」ということです。

「スコア順に並べる」だけでなく、
「スコア上位の中で順番をランダムにする」といった工夫がしやすくなります。

例2:A/B テスト用にユーザーをランダムに振り分ける

ユーザー一覧をシャッフルしてから、
先頭 50% を A グループ、残りを B グループ、のように分けることもできます。

List<User> users = findAllUsers();

List<User> randomized = Shuffles.shuffledCopy(users);

int half = randomized.size() / 2;
List<User> groupA = randomized.subList(0, half);
List<User> groupB = randomized.subList(half, randomized.size());
Java

ここでの重要ポイントは、
「シャッフル+分割で、“ランダムなグループ分け”を簡単に表現できる」ことです。


シャッフルの注意点

「本当にランダムでいいのか?」を一度立ち止まって考える

シャッフルは便利ですが、
「毎回ランダムでいいのか?」は一度立ち止まって考えた方がいいポイントです。

ログの追跡が難しくなる。
ユーザー体験として「毎回変わる」のが逆に不安になるケースもある。
キャッシュが効きにくくなる。

業務では、「どこまでランダムにするか」「どこは固定順にするか」を設計として決めることが大事です。
シャッフルユーティリティはあくまで“道具”であって、
「どこで使うか」はビジネス側の意図とセットで考える必要があります。


まとめ:シャッフルユーティリティで身につけてほしい感覚

シャッフルは、
単に「順番をぐちゃぐちゃにするテクニック」ではなく、
「順番に意味を持たせない/バイアスを減らすための設計」です。

Collections.shuffle は List を破壊的に並べ替えるので、元の順番を残したいときはコピーしてから使う。
Shuffles.shuffledCopy のようなユーティリティにして、null・空・コピー・シャッフル方法を一箇所に閉じ込める。
Random のシードを固定することで、「再現性のあるランダム」をテストなどで使える。
おすすめ順・A/B テスト・ランダムグループ分けなど、「順番のバイアスを減らしたい場面」で意識的に使う。

あなたのコードのどこかに、
「とりあえず Collections.shuffle を直書きしている」箇所があれば、
それを一度「シャッフルユーティリティ+コピー」に整理できないか眺めてみてください。

その小さな整理が、
「順番とランダムさを、意図を持ってコントロールできるエンジニア」への、
確かな一歩になります。

タイトルとURLをコピーしました