カスタムCollectorの作り方と活用法|Javaストリーム処理をさらに強力にする実践テクニック

Java

はじめに:なぜカスタムCollectorが必要なのか?

Javaの Stream API を使うと、データ処理がとてもシンプルになります。
一覧をまとめるだけなら toList()joining() など標準のCollectorで十分です。しかし、実際の現場では「もっと複雑な処理」を一度のストリーム操作で行いたい場面が多くあります。

例えば:

  • 複数の統計値を同時に計算したい
  • 独自のオブジェクトへまとめたい
  • 標準Collectorでは表現しづらい集約ロジックを使いたい

そんなときに役立つのが “カスタムCollector” です。

この記事では、プログラミング初心者でも理解できるよう、優しい説明でまとめています。
Javaを武器にしてキャリアアップしたい人にもおすすめの内容です。


Collectorの基本構造を理解しよう

Collector の型はこう表されます:

Collector<T, A, R>

それぞれの意味は以下の通りです:

パラメータ役割
T入力(ストリームの要素)
A中間バッファ(処理途中の保持データ型)
R出力(最終的な結果)

Collectorは以下のメソッドを実装します:

メソッド役割
supplier()中間バッファの初期生成
accumulator()要素を中間バッファへ追加
combiner()並列処理時にバッファを結合
finisher()最終結果に整形
characteristics()Collectorの性質定義

このうち初めの四つが最重要です。


カスタムCollectorの作り方【基本編】

方法1:Collectorインタフェースを実装する

もっとも自由度が高い方法です。

◆例:全要素を大文字に変換して、不変リストで返すCollector

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();
    }
}

利用例:

List<String> result = names.stream()
    .collect(new ToUppercaseImmutableListCollector());

カスタムCollectorの作り方【簡易編】

方法2:Collector.of(…) を使う

よりシンプルに書けます。

◆例:合計値と平均値を1回のストリーム操作で計算するCollector

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]
    )
);

使い方:

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プログラマーになりたい人へ。


それでも難しい…という人へ

  • ソースコードのレビューがほしい
  • 現場レベルのスキルを効率的に学びたい
  • プログラマー転職のサポートも受けたい

そんな方には サイゼントアカデミー をおすすめします。

👉 サイゼントアカデミー

学習ロードマップ、コード添削、転職支援まで揃った「実務で使えるJavaスキル」を身につけるには最適です。


まとめ

  • カスタムCollectorは「データ処理を1回でまとめたい」ときに大きな力を発揮
  • Collectorの仕組みを理解すれば、どんな集約処理も自由自在
  • Javaの強みは「実務で使える高品質な標準ライブラリ」
  • Javaの技術を伸ばせば、プログラマーとしての市場価値は大きく上がる

カスタムCollectorを使いこなせるようになると、Javaのストリーム処理がもっと楽しく、もっと強力になります。
ぜひあなたのプロジェクトでも活用してみてください。

コメント

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