Java:Streamのreduceの使用方法

JavaのStreamにreduceという機能があるが、この使い方を解説してみる。

reduceその1

最も簡単な例

要するにストリームで得られた要素を合計するものと思えばよい。

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

public class Sample {
  public static void main(String[]args) {

    // Integerのストリームを作成する
    Stream<Integer>stream = Arrays.stream(new Integer[] { 1, 2, 3, 4});

    // 合計を求める
    int total = stream.reduce((accum, value)->accum + value).get(); 

    // 表示 ---> total=10
    System.out.println("total=" + total);    
  }
}

※整数の合計を出したいなら、本来はmapToIntを使用してIntStreamのsum()を使えば良いことだが、ここではあえて面倒な方法を使っている。

ここで分かりづらいのは、「(accum, value)->accum + value」というラムダ式なのだが、以下に変更してみると良くわかる。

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

public class Sample {

  public static void main(String[]args) {

    // Integerのストリームを作成する
    Stream<Integer>stream = Arrays.stream(new Integer[] { 1, 2, 3, 4});

    // 合計を求める
    int total = stream.reduce(Sample::reduce).get(); 

    // 表示
    System.out.println("" + total);    
  }

  static int reduce(int accum, int value) {
    System.out.println(accum + "," + value);
    return accum + value;
  }
}

この結果は以下になる。

1,2
3,3
6,4
total=10

accumのの初期値は最初の要素になっているが、次の要素、その次の要素が次々に加算されていく。

Javadocの説明

以下のように説明されている。

上記のコード例をアレンジしてわかりやすくしてみる。なお、以下の例でaccumulatorと言っているのは、上述の例の場合「 static int reduce(int accum, int value)」のことだ。

  // 何らかの要素があったか
  boolean foundAny = false;
  // 最終結果
  T result = null;
  for (T element : このストリームの要素を列挙する) {
    if (!foundAny) {
      // 最初の要素の場合
      foundAny = true;
      result = element;
    } else {
      // 二番目以降の要素の場合
      result = accumulator.apply(result, element);
    }
  }
  // 結果があればそれを返す。なければ空を返す
  return foundAny ? Optional.of(result) : Optional.empty();

複数の整数集合の和集合を求める例

こんな例が考えられるだろう。整数集合のストリームがあるので、その和集合を求める。

    Stream<Set<Integer>>stream = ....;     
    Set<Integer>allSet = stream.reduce((total, value)-> {
      total.addAll(value);
      return total;
    }).get(); 

もし、ストリーム中の各集合を変更したくないのであれば以下だ。

    Stream<Set<Integer>>stream = ....
    Set<Integer>allSet = stream.reduce((total, value)-> {
      Set<Integer>tmp = new HashSet<Integer>(total);
      tmp.addAll(value);
      return tmp;
    }).get();  

reduceその2

先に示したreduceメソッドの他に、「T reduce(T identity, BinaryOperator accumulator)」というメソッドもあるが、これもほぼ同じだが、こちらの方が簡単だ。javadocの説明をアレンジしてみるとこうなる。

  // 初期値
  T result = identity;
  for (T element : このストリームの要素を列挙する) {
     // 初期値に加算していく
     result = accumulator.apply(result, element);
  }
  return result;

初期値が与えられているので「値が存在しない」ということは無く、返り値はOptionalではない。

  public static void main(String[]args) {    
    Stream<Integer>stream = Arrays.stream(new Integer[] { 1, 2, 3, 4});    
    int total = stream.reduce(10, (accum, value)->accum + value);

    // total=20
    System.out.println("total=" + total);
  }