Java 逆引き集 | reduce の使い方(集約) — 合算や累積計算

Java Java
スポンサーリンク

reduce の使い方(集約) — 合算や累積計算

Stream の「結果をひとつに畳み込む」終端操作が reduce。合計・積・最大最小・連結・カスタム集約まで、柔軟に一行で書けます。初心者が迷う「初期値あり/なし」「並列対応」のポイントを、具体例でかみ砕いて整理します。


基本の考え方

  • 役割: ストリームの全要素を「ひとつの値」に畳み込む(合算・連結・集計)。
  • 形の違い:
    • 初期値なし: stream.reduce(binaryOperator) → 結果は Optional。空ストリームで値がない可能性がある。
    • 初期値あり: stream.reduce(identity, binaryOperator) → 常に値が返る(空なら identity がそのまま返る)。
    • 並列対応3引数: stream.reduce(identity, accumulator, combiner) → 並列で部分結果を安全に結合。

すぐ試せる基本例

合計(初期値あり:常に答えが出る)

import java.util.*;
import java.util.stream.*;

List<Integer> nums = List.of(1,2,3,4,5);

int sum = nums.stream()
              .reduce(0, (a, b) -> a + b); // identity=0
System.out.println(sum); // 15
Java

合計(初期値なし:空の可能性を考慮)

Optional<Integer> maybeSum = nums.stream()
                                 .reduce((a, b) -> a + b);
System.out.println(maybeSum.orElse(0)); // 15(空なら0)
Java

文字列連結(区切りなし)

List<String> words = List.of("Java","Stream","Reduce");
String joined = words.stream()
                     .reduce("", (a, b) -> a + b); // identity=""
System.out.println(joined); // JavaStreamReduce
Java

例題で理解する

例題1: 最大値・最小値の取得

List<Integer> nums = List.of(7, 2, 9, 4);

// 最大
int max = nums.stream().reduce(Integer.MIN_VALUE, Math::max);

// 最小(初期値なし)
int min = nums.stream().reduce(Math::min).orElse(Integer.MAX_VALUE);

System.out.println(max); // 9
System.out.println(min); // 2
Java
  • ポイント: 初期値ありだと必ず値が返る。なしだと Optional で安全に扱える。

例題2: 商品金額の総計(税計算を含む)

record Item(String name, int price) {}

List<Item> items = List.of(new Item("A",100), new Item("B",250), new Item("C",160));

int totalWithTax = items.stream()
    .map(i -> (int)Math.round(i.price() * 1.1)) // 税込みへ変換
    .reduce(0, Integer::sum);                   // 合計
System.out.println(totalWithTax); //  (110 + 275 + 176) の合計
Java
  • ポイント: reduce 前に map で「計算ルール」を適用してから畳み込む。

例題3: 文字列連結(区切りを挟む)

List<String> names = List.of("Tanaka", "Sato", "Ito");

// 先頭に余計な区切りを付けないため、初期値なしで開始
String joined = names.stream()
    .reduce((a, b) -> a + ", " + b)
    .orElse(""); // 空なら空文字
System.out.println(joined); // Tanaka, Sato, Ito
Java
  • ポイント: 区切り付き連結は「初期値なし」がスマート。

例題4: 並列計算のための3引数 reduce

import java.util.stream.*;
import java.util.*;

List<Integer> nums = IntStream.rangeClosed(1, 1_000_000).boxed().toList();

int sum = nums.parallelStream()
    .reduce(0,
        (partial, x) -> partial + x, // accumulator(各スレッド内)
        Integer::sum                 // combiner(部分結果どうし)
    );
System.out.println(sum);
Java
  • ポイント: 並列時は「分割→部分集約→結合」を定義する3引数形式が安全。

実用レシピ

  • 合計(プリミティブストリームが最速)
int sum = list.stream().mapToInt(x -> x).sum(); // reduce より簡潔・高速
Java
  • 積(空なら1を返す)
int product = nums.stream().reduce(1, (a, b) -> a * b);
Java
  • フラグ集合の AND/OR 畳み込み
boolean allOk = flags.stream().reduce(true, (a, b) -> a && b);
boolean anyOk = flags.stream().reduce(false, (a, b) -> a || b);
Java
  • カスタム集約(合計・件数を同時に)→ まとめて平均
record Agg(long sum, long count) {}
Agg agg = nums.stream()
    .reduce(new Agg(0,0),
        (a, x) -> new Agg(a.sum()+x, a.count()+1),
        (a, b) -> new Agg(a.sum()+b.sum(), a.count()+b.count()));
double avg = agg.count() == 0 ? 0.0 : (double) agg.sum() / agg.count();
Java

テンプレート集(そのまま使える形)

  • 初期値なし(Optional で安全)
Optional<T> r = stream.reduce((a, b) -> combine(a, b));
Java
  • 初期値あり(必ず値が返る)
T r = stream.reduce(identity, (acc, x) -> combine(acc, x));
Java
  • 並列対応(3引数)
T r = stream.parallel()
    .reduce(identity,
            (acc, x) -> accumulate(acc, x),
            (left, right) -> combine(left, right));
Java
  • 文字列連結(区切り付き)
String s = stream.reduce((a, b) -> a + sep + b).orElse("");
Java

よくある落とし穴と回避策

  • 空ストリームで例外/不適切な初期値:
    • 回避: 初期値なしなら Optional を使う。初期値ありなら意味のある identity(合計なら0、積なら1)にする。
  • 非結合(associative)な演算で並列バグ:
    • 回避: 並列 reduce は演算が結合的であることが前提。順序依存や外部副作用を入れない。
  • 文字列連結の低速化:
    • 回避: 大量連結は Collectors.joining(", ") の方が速く簡潔。
  • reduce の過剰使用:
    • 回避: 合計・平均・最大最小は mapToInt().sum(), average(), max() など専用終端を優先。

まとめ

  • reduce は「ストリームをひとつの値に畳み込む」ための終端操作。初期値あり/なし、並列対応の3引数を使い分けると安全で強力。
  • 専用メソッド(sum/average/max/joining)がある場面はそちらを優先し、reduce は「カスタム集約」に最も威力を発揮する。
  • 結合的な演算・意味のある初期値・Optional の活用を押さえれば、実務でも安心して使える。
タイトルとURLをコピーしました