はじめに:なぜカスタムCollectorが必要なのか?
Javaの Stream API を使うと、データ処理がとてもシンプルになります。
一覧をまとめるだけなら toList() や joining() など標準のCollectorで十分です。しかし、実際の現場では「もっと複雑な処理」を一度のストリーム操作で行いたい場面が多くあります。
例えば:
- 複数の統計値を同時に計算したい
- 独自のオブジェクトへまとめたい
- 標準Collectorでは表現しづらい集約ロジックを使いたい
そんなときに役立つのが “カスタムCollector” です。
この記事では、プログラミング初心者でも理解できるよう、優しい説明でまとめています。
Javaを武器にしてキャリアアップしたい人にもおすすめの内容です。
Collectorの基本構造を理解しよう
Collector の型はこう表されます:
|
1 2 |
Collector<T, A, R> |
それぞれの意味は以下の通りです:
| パラメータ | 役割 |
|---|---|
| T | 入力(ストリームの要素) |
| A | 中間バッファ(処理途中の保持データ型) |
| R | 出力(最終的な結果) |
Collectorは以下のメソッドを実装します:
| メソッド | 役割 |
|---|---|
| supplier() | 中間バッファの初期生成 |
| accumulator() | 要素を中間バッファへ追加 |
| combiner() | 並列処理時にバッファを結合 |
| finisher() | 最終結果に整形 |
| characteristics() | Collectorの性質定義 |
このうち初めの四つが最重要です。
カスタムCollectorの作り方【基本編】
方法1:Collectorインタフェースを実装する
もっとも自由度が高い方法です。
◆例:全要素を大文字に変換して、不変リストで返すCollector
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public class ToUppercaseImmutableListCollector implements Collector<String, List<String>, List<String>> { @Override public Supplier<List<String>> supplier() { return ArrayList::new; } @Override public BiConsumer<List<String>, String> accumulator() { return (list, element) -> list.add(element.toUpperCase()); } @Override public BinaryOperator<List<String>> combiner() { return (l1, l2) -> { l1.addAll(l2); return l1; }; } @Override public Function<List<String>, List<String>> finisher() { return list -> List.copyOf(list); } @Override public Set<Characteristics> characteristics() { return Set.of(); } } |
利用例:
|
1 2 3 |
List<String> result = names.stream() .collect(new ToUppercaseImmutableListCollector()); |
カスタムCollectorの作り方【簡易編】
方法2:Collector.of(…) を使う
よりシンプルに書けます。
◆例:合計値と平均値を1回のストリーム操作で計算するCollector
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class Stats { private int sum; private double average; public Stats(int sum, double average) { this.sum = sum; this.average = average; } // getter省略 } Collector<Integer, int[], Stats> statsCollector = Collector.of( () -> new int[]{0, 0}, // {合計, カウント} (buffer, value) -> { buffer[0] += value; buffer[1]++; }, (b1, b2) -> { b1[0] += b2[0]; b1[1] += b2[1]; return b1; }, buffer -> new Stats( buffer[0], buffer[1] == 0 ? 0 : (double) buffer[0] / buffer[1] ) ); |
使い方:
|
1 2 |
Stats stats = numbers.stream().collect(statsCollector); |
一度のストリーム処理で 合計と平均を同時に 取り出せるのがポイントです。
活用例:実務で使えるカスタムCollector
◆活用1:複数の計算を1ストリームでまとめたい
例:売上合計、注文数、平均単価、最大値などを同時に求める。
◆活用2:プロジェクト固有の型にまとめたい
データベースから取得したエンティティを、サービス層向けのDtoに一括変換するなど。
◆活用3:複雑なグルーピングやセット構造を作りたい
標準Collectorだと書きにくい独自のマップ構造などを生成可能。
標準CollectorとカスタムCollectorの使い分け
標準Collectorで十分なケース
- リスト化、セット化、マップ化
- グルーピング
- 集約(sum、avg、maxなど)
カスタムCollectorが必要なケース
- 1回のストリーム処理で複数の結果を取りたい
- 標準Collectorで表現しにくいロジックが必要
- 自作のデータ構造に変換したい
過剰なカスタムは逆効果
- 他人が読めないコードはプロジェクトでは負担
- 並列ストリームの場合、スレッド安全性の考慮が必須
「本当に必要なときだけ作る」
これがカスタムCollectorの鉄則です。
Javaを学ぶなら、まず基本+実践が最重要
カスタムCollectorのような少し高度なテクニックは、Javaを本気で扱う人には非常に役立ちます。
ここで一つだけ重要なアドバイスです。
📚 本気でJavaプログラマーになりたいなら
まずは以下の書籍で自己学習を固めてください。
これは本気でJavaエンジニアを目指す人向けです。
それでも難しい…という人へ
- ソースコードのレビューがほしい
- 現場レベルのスキルを効率的に学びたい
- プログラマー転職のサポートも受けたい
そんな方には サイゼントアカデミー をおすすめします。
学習ロードマップ、コード添削、転職支援まで揃った「実務で使えるJavaスキル」を身につけるには最適です。
まとめ
- カスタムCollectorは「データ処理を1回でまとめたい」ときに大きな力を発揮
- Collectorの仕組みを理解すれば、どんな集約処理も自由自在
- Javaの強みは「実務で使える高品質な標準ライブラリ」
- Javaの技術を伸ばせば、プログラマーとしての市場価値は大きく上がる
カスタムCollectorを使いこなせるようになると、Javaのストリーム処理がもっと楽しく、もっと強力になります。
ぜひあなたのプロジェクトでも活用してみてください。

コメント