Java - Stream.reduce()の使い方と例

Java 8 で導入された Stream の reduce() の使い方と例を紹介します。

1. Stream.reduce()

Stream.reduce(accumulator) 関数は、Stream の要素を 1 つのデータにする作業を行います。

たとえば、Stream から 1 から 10 までの数値が渡されると、この値を合計して 55 の結果を返すことができます。 ここで演算を行う部分はアキュムレータ関数であり、直接実装して引数に渡す必要があります。

以下は、 reduce() を使ってストリームから渡される要素の数をすべて合計する例です。

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = numbers.reduce((x, y) -> x + y);
sum.ifPresent(s -> System.out.println("sum: " + s));

Output:

sum: 55

上記の例を説明しながら reduce() の動作方法について紹介します。

reduce() は引数として BinaryOperator オブジェクトを受け取り、 BinaryOperator は T 型の引数を 2 つ受け取り、T 型のオブジェクトを返す関数型インタフェースです。

BinaryOperatorは (total, n) -> total + nと同じ形式で引数が渡されます。 Stream の 1 が渡されるとき、total(0) + n(1) = 1 のように計算され、ここで返される 1 が次に Stream で 2 が渡されるとき total に渡されます。したがって、 total(1) + n(2) = 3 になります。

もう一度整理すると、次のように計算され、最後に1から10までの数字を加えた55が返されます。

  • total(0) + n(1) = 1
  • total(1) + n(2) = 3
  • total(3) + n(3) = 6
  • total(6) + n(4) = 10
  • total(10) + n(5) = 15
  • total(15) + n(6) = 21
  • total(21) + n(7) = 28
  • total(28) + n(8) = 36
  • total(36) + n(9) = 45
  • total(45) + n(10) = 55

1.1 メソッドリファレンスとして実装

(x, y) -> x + y のように和を計算する関数は、JDK で Integer.sum(a, b) という基本関数を提供します。

ここでメソッドリファレンスを使うと、 Integer::sum のように、より短いコードで同じ結果を返すコードを実装できます。

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = numbers.reduce(Integer::sum);
sum.ifPresent(s -> System.out.println("sum: " + s));

2. 初期値のある Stream.reduce()

上記の例では、totalの初期値は0でした。

しかし、 Stream.reduce(init, accumulator) のように初期値を引数として渡すことができます。

上記の例で初期値を10に設定すると、「10 + 1 + 2 + 3 ... 10」のように演算が行われます。

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = numbers.reduce(10, (total, n) -> total + n);
System.out.println("sum: " + sum);

Output:

sum: 65

3. reduce() の並列処理

Stream.parallel() は、Stream 演算を並列処理で行うようにします。つまり、 parallel() とともに reduce() を使用すると、順次演算を実行せずに複数の演算を同時に進行し、それらの作業を再びマージして最終的に 1 つの結果を生成します。

たとえば、 (1 + 2) + (3 + 4) + ... + (9 + 10)のように2つずつ束ねて最初に計算し、結果を再計算できます。

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = numbers.parallel().reduce(0, (total, n) -> total + n);
System.out.println("sum: " + sum);

Output:

sum: 55

ただし、減算演算の場合、並列処理は順次処理(並列ではない)と結果が異なります。 以下のコードを実行すると、-55ではなく-5が返されます。 結果が異なる理由は、 (1 - 2) - (3 - 4) - ... - (9 - 10) のように演算が行われながら順次演算することと結果が変わるからです。

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = numbers.parallel().reduce(0, (total, n) -> total - n);
System.out.println("sum: " + sum);

Output:

sum: -5

並列処理の過程で reduce() を使うときは、上記のような問題がないことを確認する必要があります。

4. 並列処理で reduce() は順次処理

並列処理での操作順序によって発生する問題を解決するために、以下の例に示すように他の規則を追加できます。

上記の例と比較すると、 (total1, total2) -> total1 + total2が追加され、並列に処理された結果の関係を示します。 もう一度説明すると、最初の演算と2番目の演算は合わなければならないというルールを追加したのです。このようにルールを追加すると、最初の演算の結果が次の演算に影響を与えるため、 reduce() は操作を分割して処理できなくなります。

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = numbers.reduce(0,
        (total, n) -> total - n,
        (total1, total2) -> total1 + total2);
System.out.println("sum: " + sum);

Output:

sum: -55

Related Posts

codechachaCopyright ©2019 codechacha