【Java学習|実務向け】Stream APIを使いこなす!Collectorsによる集計処理の最適解

1. 導入:なぜCollectorsによる集計が重要なのか

JavaのStream APIにおいて、データの集計は頻繁に行う処理の一つです。しかし、ループ処理で手書きの集計ロジックを書くと、コードの可読性が下がり、バグの温床にもなりがちです。Java 8から導入されたCollectorsクラスのメソッドを活用することで、宣言的かつ簡潔に集計処理を実現できます。本記事では、実務で頻出する集計用Collectorsを整理し、使い分けの基準を解説します。

2. 基礎知識:Collectorsとは何か

Collectorsは、Streamの終端処理であるcollectメソッドに渡す「集計戦略」を定義するユーティリティクラスです。例えば、リスト内の数値を合計したり、最大値を取り出したりする処理を、ライブラリの機能として提供します。これにより、開発者は「何を計算するか」に集中でき、計算の複雑さを隠蔽することが可能です。

3. 実装/解決策:各メソッドの役割と使い分け

用途に応じて最適なメソッドを選択する必要があります。
reducing: 汎用的な集計(畳み込み)。初期値、マッピング、結合処理を指定可能。
minBy / maxBy: Comparatorを基に最小値・最大値を取得。結果はOptionalで返されます。
summingInt / averagingInt: 数値型の合計・平均。Int以外の型(Long, Double)にも対応するバリエーションがあります。
summarizingInt: 件数、合計、最小、最大、平均を一度に取得。統計情報をまとめて出したい場合に最適です。

4. サンプルプログラム

以下のコードは、従業員リストから給与データを集計する実務的な例です。

import java.util.;
import java.util.stream.Collectors;

public class StreamCollectorSample {
    record Employee(String name, int salary) {}

    public static void main(String[] args) {
        List employees = List.of(
            new Employee("田中", 300000),
            new Employee("佐藤", 450000),
            new Employee("鈴木", 250000)
        );

        // 1. 合計を取得
        int totalSalary = employees.stream().collect(Collectors.summingInt(Employee::salary));
        System.out.println("総額: " + totalSalary);

        // 2. 最大値を取得
        employees.stream()
            .collect(Collectors.maxBy(Comparator.comparingInt(Employee::salary)))
            .ifPresent(e -> System.out.println("最高給与者: " + e.name()));

        // 3. 統計情報をまとめて取得(件数, 合計, 最小, 最大, 平均)
        IntSummaryStatistics stats = employees.stream()
            .collect(Collectors.summarizingInt(Employee::salary));
        System.out.println("平均給与: " + stats.getAverage());
        System.out.println("最高額: " + stats.getMax());

        // 4. reducingによる柔軟な集計(例: 給与の合計を計算)
        int reducedSalary = employees.stream()
            .map(Employee::salary)
            .collect(Collectors.reducing(0, Integer::sum));
        System.out.println("Reducingによる合計: " + reducedSalary);
    }
}

5. 応用・注意点:現場でのベストプラクティス

nullへの対策:
データソースにnullが含まれる可能性がある場合、事前にfilter(Objects::nonNull)を挟むか、Optionalを適切に扱う必要があります。

プリミティブ型特化Streamの検討:
Collectors.summingIntなどは便利ですが、mapToInt()を使用してIntStreamに変換してからsum()を呼ぶ方が、ボクシング(Integerオブジェクトへの変換)が発生せず、パフォーマンス面で有利な場合があります。

可読性の重視:
複雑なreducingを無理に一行で書くよりも、可読性が低いと感じたら、一度中間変数に切り出すか、従来のループ処理を選択する勇気も必要です。チーム開発では「誰が見ても意図が伝わるか」を最優先してください。

コメント

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