Java 逆引き集 | Collector.of でカスタム Collector 作成 — 特殊な集約

Java Java
スポンサーリンク

Collector.of でカスタム Collector 作成 — 特殊な集約

Stream API の Collectors には便利な標準 Collector が多数ありますが、特殊な集約処理をしたいときは Collector.of を使って自作できます。
初心者がつまずきやすい「Collector の仕組み」を、コード例とテンプレートでかみ砕いて説明します。


Collector の基本構造

Collector.of は以下の 3〜4 つの関数を渡して作ります:

Collector.of(
    Supplier<A> supplier,          // 初期コンテナの生成
    BiConsumer<A, T> accumulator,  // 要素をコンテナに追加する方法
    BinaryOperator<A> combiner,    // 並列処理時に部分結果を結合する方法
    Function<A, R> finisher        // 最終結果への変換(省略可)
);
Java
  • supplier: 集約のための「入れ物」を作る。例: ArrayList::new
  • accumulator: 要素をどう入れるか。例: (list, e) -> list.add(e)
  • combiner: 並列処理で部分結果をどうまとめるか。例: (l1, l2) -> { l1.addAll(l2); return l1; }
  • finisher: 最終的に結果をどう変換するか。省略すると「入れ物そのまま」が返る。

基本コード例

1) リストに集約(標準 toList と同じ)

Collector<String, List<String>, List<String>> toListCustom =
    Collector.of(
        ArrayList::new,                  // supplier
        List::add,                       // accumulator
        (l1, l2) -> { l1.addAll(l2); return l1; }, // combiner
        Function.identity()              // finisher
    );

List<String> result = Stream.of("a","b","c").collect(toListCustom);
System.out.println(result); // [a, b, c]
Java

2) 文字列を連結(区切り付き)

Collector<String, StringBuilder, String> joiningWithComma =
    Collector.of(
        StringBuilder::new,
        (sb, s) -> {
            if (sb.length() > 0) sb.append(", ");
            sb.append(s);
        },
        (sb1, sb2) -> {
            if (sb1.length() == 0) return sb2;
            if (sb2.length() > 0) sb1.append(", ").append(sb2);
            return sb1;
        },
        StringBuilder::toString
    );

String joined = Stream.of("apple","banana","cherry").collect(joiningWithComma);
System.out.println(joined); // apple, banana, cherry
Java

3) 合計と件数を同時に計算(平均値を出す)

record Stats(int sum, int count) {}

Collector<Integer, Stats, Double> avgCollector =
    Collector.of(
        () -> new Stats(0,0),
        (st, n) -> { st = new Stats(st.sum()+n, st.count()+1); },
        (s1, s2) -> new Stats(s1.sum()+s2.sum(), s1.count()+s2.count()),
        st -> st.count() == 0 ? 0.0 : (double) st.sum()/st.count()
    );
Java

※ 上記はイミュータブル record を使った例。可変クラスならフィールド更新で書けます。


例題で理解する

例題1: ユニークな要素を保持する Collector

Collector<String, Set<String>, Set<String>> toUniqueSet =
    Collector.of(
        HashSet::new,
        Set::add,
        (s1, s2) -> { s1.addAll(s2); return s1; },
        Function.identity()
    );

Set<String> uniq = Stream.of("a","b","a","c").collect(toUniqueSet);
System.out.println(uniq); // [a, b, c]
Java

例題2: 文字数合計を Collector で作る

Collector<String, int[], Integer> lengthSum =
    Collector.of(
        () -> new int[1],                // supplier
        (acc, s) -> acc[0] += s.length(),// accumulator
        (a1, a2) -> { a1[0] += a2[0]; return a1; }, // combiner
        acc -> acc[0]                    // finisher
    );

int totalLen = Stream.of("Java","Stream","Collector").collect(lengthSum);
System.out.println(totalLen); // 18
Java

テンプレート集

  • リスト化
Collector<T, List<T>, List<T>> toListCustom =
    Collector.of(ArrayList::new, List::add,
                 (l1,l2)->{ l1.addAll(l2); return l1; },
                 Function.identity());
Java
  • 文字列連結
Collector<String, StringBuilder, String> joining =
    Collector.of(StringBuilder::new,
                 (sb,s)->{ if(sb.length()>0) sb.append(","); sb.append(s); },
                 (sb1,sb2)->{ sb1.append(",").append(sb2); return sb1; },
                 StringBuilder::toString);
Java
  • 数値集計(合計)
Collector<Integer, int[], Integer> sumCollector =
    Collector.of(() -> new int[1],
                 (acc,n)->acc[0]+=n,
                 (a1,a2)->{a1[0]+=a2[0]; return a1;},
                 acc->acc[0]);
Java

落とし穴と回避策

  • supplier/accumulator/combiner の役割混同: supplier は「新しい入れ物」、accumulator は「要素追加」、combiner は「並列時の結合」。役割を明確に。
  • 可変 vs 不変: 可変オブジェクト(ArrayList, StringBuilder)ならフィールド更新で簡単。不変なら新しいオブジェクトを返す必要がある。
  • 並列処理: combiner が正しくないと並列ストリームで誤動作。必ず「部分結果を正しく結合」できるようにする。
  • finisher の省略: 結果が「入れ物そのまま」で良いなら Function.identity() を指定。

まとめ

  • Collector.of は「特殊な集約」を自作するための仕組み。
  • supplier / accumulator / combiner / finisher の4つを理解すれば、どんな集約も作れる。
  • 標準 Collector にない「特殊な集計・変換」を Collector.of で安全に表現できる。

👉 練習課題: 「文字列リストから、文字数合計と平均を同時に計算する Collector」を作ってみると、Collector.of の仕組みが直感で理解できます。

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