StreamAPIについて調べてみた reduce編 その1
【前提条件】
【概要】
今回はreduceについて調べてみました。
reduceはStreamの要素を集計した結果に対して、
何かしらの処理行い一つの結果を得るメソッドです。
たとえば、List内の要素の合計を求めるなどを行う場合に使います。
【reduceメソッド】
Stream#reduceメソッドには3種類用意されています。
Optional<T> reduce(BinaryOperator<T> accumulator);
T reduce(T identity, BinaryOperator<T> accumulator);
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
パラメータがそれぞれ1個〜3個のメソッドが用意されています。
一つずつ見ていきます。
今回のサンプルは文字列を結合するものを作成します。
【パラメータが1つのメソッド】
[サンプルソース]
public static void main(final String[] args) { final List<String> list = new ArrayList<>(); list.add("one"); list.add("two"); list.add("three"); final Optional<String> result = list.stream().reduce( (accum, value) -> { return accum + "-" + value; }); System.out.println(result.orElse("")); }
BinaryOperatorのオブジェクトがパラメータなります。
内部では途中結果を保持するためのアキュムレータと
要素の値がパラメータとして受け取る、BinaryOperatorのオブジェクトが生成されます。
最初にreduceが実行される段階ではaccumが"one"、valueが"two"で実行されます。
2回目はaccumが"one-two"、valueが"three"で実行されます。
戻り値はOptional
[実行結果]
one-two-three
【パラメータが2つのメソッド】
[サンプルソース]
public static void main(final String[] args) { final List<String> list = new ArrayList<>(); list.add("one"); list.add("two"); list.add("three"); final BinaryOperator<String> operation = (accum, value) -> { return accum + "-" + value; }; final String result1 = list.stream().reduce("value", operation); final String result2 = new ArrayList<String>().stream().reduce("value", operation); System.out.println(result1); System.out.println(result2); }
パラメータが2つの場合はStream内要素と同じ型のオブジェクトと
BinaryOperatorのオブジェクトがパラメータなります。
1つめパラメータはアキュムレータの初期値になります。
パラメータが2つの場合は1つめのパラメータに渡した値が
アキュムレータの初期値になります。
パラメータが1つの場合は自動的に最初の要素が
アキュムレータの初期値となっていました。
2つめのパラメータは実際に処理を行うBinaryOperatorです。
これはパラメータが1つの時と変わりません。
戻り値の方はStream内のオブジェクトと同じ型が帰ってきます。
Optionalになっていないのは
おそらく最初に渡したパラメータの値は設定されるので、
Nullの可能性がないためだと思います。
(明示的にNullを渡した場合はNullPointerExceptionが発生しましが・・・)
[実行結果]
value-one-two-three value
【パラメータが3つのメソッド】
[サンプルソース]
public static void main(final String[] args) { final List<String> list = new ArrayList<>(); list.add("one"); list.add("two"); list.add("three"); final BiFunction<String, String, String> operation = (accum, value) -> { return accum + "-" + value; }; final BinaryOperator<String> combiner = (value1, value2) -> { System.out.println("combine!"); return value1 + "+" + value2; }; final String result1 = list.stream().reduce("value", operation, combiner); System.out.println(result1); System.out.println("====================================================="); final String reuslt2 = list.stream().parallel().reduce("value", operation, combiner); System.out.println(reuslt2); }
パラメータが3つの場合は↓の値を渡します。
1:Stream内要素と同じ型のオブジェクト
2:パラメータ、戻り値が全て1の型と同じBiFunctionオブジェクト
3:型パラメータが1の型と同じBinaryOperatorオブジェクト
1はアキュムレータの初期値です。
パラメータが2つの時と同様です。
2は単一の結果を得るための処理を行うFiFunctionオブジェクトです。
パラメータが2つだった場合の、BinaryOperatorオブジェクトとほぼ同じです。
Stream#paralleメソッドで実行した際に動きがわかるので要注意です。
3はStream#paralleメソッドで並行処理が行われる場合にのみ実行されます。
BinaryOperatorのパラメータには2の実行結果の値が2つ渡されます。
[実行結果]
value-one-two-three ===================================================== combine! combine! value-one+value-two+value-three
シリアルに処理している場合はoperation内部の処理が実行されて
各値が"-"で結合されているのがわかります。
並行に処理している場合、2回ほど"combine!"が出力されているので、
combiner内の処理が行われているのがわかります。
処理結果がシリアルに処理している場合と異なります。
並行処理の場合は2のBiFunctionオブジェクトには
1の値とStream内のオブジェクトがパラメータとして渡されるようです。
サンプルでは"value"とStream内のオブジェクトが渡されます。
2の実行した結果を3のBinaryOperatorのパラメータとして
受け取り実行されます。
【まとめ】
reduceメソッドはStream内のオブジェクトに対して
何かしらの加工・計算を行い、集計した結果が帰ってくることがわかりました。
一つ問題なのはパラメータが3つの場合で
並行処理を行う場合、パラメータの役割が変わってしまうので
注意が必要になることでしょうか。
今回のサンプルが悪かったかなと思う部分もありますが・・・
数値計算であれば、アキュムレータに0を渡せば
影響はなく処理を行えると思うので、
アキュムレータに何を渡すかは慎重に考える必要がありそうですね。