reduce()
メソッドが Java-8 でどのように機能するかを理解しようとしています。
たとえば、私はこのコードを持っています:
public class App {
public static void main(String[] args) {
String[] arr = {"lorem", "ipsum", "sit", "amet"};
List<String> strs = Arrays.asList(arr);
int ijk = strs.stream().reduce(0,
(a, b) -> {
System.out.println("Accumulator, a = " + a + ", b = " + b);
return a + b.length();
},
(a, b) -> {
System.out.println("Combiner");
return a * b;
});
System.out.println(ijk);
}
}
そして出力はこれです:
Accumulator, a = 0, b = lorem
Accumulator, a = 5, b = ipsum
Accumulator, a = 10, b = sit
Accumulator, a = 13, b = amet
17
これらの文字列の長さの合計です。また、コンバイナにはアクセスしないため、数値が乗算されず、数値が追加されるだけです。
しかし、stream
をparallelStream
に置き換えた場合:
int ijk = strs.parallelStream().reduce(0,
(a, b) -> {
System.out.println("Accumulator, a = " + a + ", b = " + b);
return a + b.length();
},
(a, b) -> {
System.out.println("Combiner");
return a * b;
});
System.out.println(ijk);
これは出力です:
Accumulator, a = 0, b = ipsum
Accumulator, a = 0, b = lorem
Accumulator, a = 0, b = sit
Combiner
Accumulator, a = 0, b = amet
Combiner
Combiner
300
アキュムレータとコンバイナの両方にアクセスしていますが、返されるのは乗算のみです。では、合計はどうなりますか?
あなたは言うreduce
のドキュメントを読むべきです:
さらに、コンバイナ機能はアキュムレータ機能と互換性がなければなりません。すべてのuとtについて、次の条件を満たす必要があります。
combiner.apply(u、accumulator.apply(identity、t))== accumulator.apply(u、t)
あなたの場合、あなたはその法則を破っています(accumulator
でsumを実行し、combiner
でmultiplicationを実行します)。このような操作は実際には定義されておらず、基になるソースのSpliteratorがどのように実装されているかによって異なります(実行しないでください)。
さらに、combiner
はonlyで、並列ストリームに対して呼び出されます。
もちろん、アプローチ全体を次のように簡略化できます。
Arrays.asList("lorem", "ipsum", "sit", "amet")
.stream()
.mapToInt(String::length)
.sum();
学習目的でそれを行っている場合、正しいreduce
は(sum
を取得するため)です。
strs.parallelStream()
.reduce(0,
(a, b) -> {
System.out.println("Accumulator, a = " + a + ", b = " + b);
return a + b.length();
},
(a, b) -> {
System.out.println("Combiner");
return a + b;
});
重要な概念:アイデンティティ、アキュムレータ、コンバイナ
Stream.reduce()operation:操作の参加要素を個別のブロックに分解してみましょう。そうすれば、それぞれが果たす役割をより簡単に理解できます
ストリームが並列実行されると、Javaランタイムはストリームを複数のサブストリームに分割します。そのような場合、関数を使用してサブストリームの結果を1つに結合する必要があります。これはコンバイナの役割
Case 1: CombinerはparallelStream
で動作し、例に示されています
ケース2:引数のタイプが異なるアキュムレータの例
この場合、Userオブジェクトのストリームがあり、アキュムレータ引数のタイプはIntegerとUserです。ただし、アキュムレータの実装は整数の合計であるため、コンパイラはユーザーパラメータのタイプを推測できません。
List<User> users = Arrays.asList(new User("John", 30), new User("Julie", 35));
int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge());
コンパイルエラー
The method reduce(User, BinaryOperator<User>) in the type Stream<User> is not applicable for the arguments (int, (<no type> partialAgeResult, <no type> user) -> {})
この問題は、コンバイナを使用して修正できます。これはメソッド参照Integer::sum
またはラムダ式を使用して(a,b)->a+b
int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge(),Integer::sum);
簡単に言うと、シーケンシャルストリームを使用し、アキュムレータ引数のタイプとその実装のタイプが一致する場合、コンバイナを使用する必要はありません。
Java-stream を使用して削減する方法は3つあります。簡単に言うと、_Stream::reduce
_は2つの結果アイテム(または最初のアイテムのID値1)から始まり、それらのアイテムを使用して操作を実行して、新しい値を減らします。次の項目ごとに同じことが起こり、値が減らされて操作が実行されます。
_'a'
_、_'b'
_、_'c'
_および_'d'
_のストリームがあるとします。削減は、次の一連の操作を実行します。
result = operationOn('a', 'b')
-operationOn
は何でもかまいません(入力の長さの合計..)result = operationOn(result, 'c')
result = operationOn(result, 'd')
result is returned
_メソッドは次のとおりです。
Optional<T> reduce(BinaryOperator<T> accumulator)
要素の削減を実行します。最初の2つのアイテムが減少した値を生成し、次に各アイテムが減少した値を生成します。入力ストリームが空でないことが保証されていないため、_Optional<T>
_が返されます。
T reduce(T identity, BinaryOperator<T> accumulator)
は、最初のアイテムとしてID値が指定されることを除いて、上記のメソッドと同じです。 _T identity
_により、常に少なくとも1つのアイテムが保証されるため、T
が返されます。
U reduce(U identity, BiFunction<U,? super T, U> accumulator, BinaryOperator<U> combiner)
は、上記の方法と同じですが、関数が組み合わされている点が異なります。 _U identity
_により、常に少なくとも1つのアイテムが保証されるため、U
が返されます。
正確に何が起こるかを確認するためのデモとして、加算と乗算を行うことを選択したと思います。
すでにお気づきのように、またすでに述べたように、コンバイナは並列ストリームでのみ呼び出されます。
つまり、並列ストラムでは、ストリームの一部(それぞれ、基になるSpliterator)が切り取られ、別のスレッドによって処理されます。いくつかの部品が処理された後、それらの結果はコンバイナで結合されます。
あなたの場合、4つの要素はすべて別のスレッドで処理され、要素ごとに結合されます。そのため、(0 +
以外の)追加が適用されず、乗算のみが表示されます。
ただし、意味のある結果を得るには、*
から+
に切り替えて、より意味のある出力を行う必要があります。