web-dev-qa-db-ja.com

Java 8の型を変換するreduceメソッドにコンバイナーが必要な理由

Streams combinerメソッドでreduceが果たす役割を完全に理解できない。

たとえば、次のコードはコンパイルされません。

int length = asList("str1", "str2").stream()
            .reduce(0, (accumulatedInt, str) -> accumulatedInt + str.length());

コンパイルエラー:(引数の不一致; intはJava.lang.Stringに変換できません)

しかし、このコードはコンパイルします:

int length = asList("str1", "str2").stream()  
    .reduce(0, (accumulatedInt, str ) -> accumulatedInt + str.length(), 
                (accumulatedInt, accumulatedInt2) -> accumulatedInt + accumulatedInt2);

コンバイナメソッドは並列ストリームで使用されることを理解しています。したがって、この例では、2つの中間累積整数を加算しています。

しかし、最初の例がコンバイナなしではコンパイルできない理由、またはコンバイナが2つのintを加算するだけなので、文字列からintへの変換をどのように解決するのかを理解できません。

誰もこれに光を当てることができますか?

116
Louise Miller

使用しようとしたreduceの2つと3つの引数バージョンは、accumulatorに同じ型を受け入れません。

2つの引数reduceは、 定義 です。

T reduce(T identity,
         BinaryOperator<T> accumulator)

あなたの場合、Tは文字列なので、BinaryOperator<T>は2つの文字列引数を受け入れ、文字列を返す必要があります。しかし、intとStringを渡すと、コンパイルエラーargument mismatch; int cannot be converted to Java.lang.Stringが発生します。実際、文字列が期待されている(T)ため、ID値として0を渡すことも間違っていると思います。

また、このバージョンのreduceはTのストリームを処理し、Tを返すため、これを使用してStringのストリームをintに減らすことはできません。

3つの引数reduce定義 です。

<U> U reduce(U identity,
             BiFunction<U,? super T,U> accumulator,
             BinaryOperator<U> combiner)

あなたの場合、Uは整数で、Tは文字列です。したがって、このメソッドは文字列のストリームを整数に減らします。

BiFunction<U,? super T,U>アキュムレーターには、2つの異なるタイプ(Uおよび?スーパーT)のパラメーターを渡すことができます。これらのパラメーターは、整数とストリングです。さらに、ID値Uは整数を受け入れますので、0を渡しても問題ありません。

あなたが望むものを達成する別の方法:

int length = asList("str1", "str2").stream().mapToInt (s -> s.length())
            .reduce(0, (accumulatedInt, len) -> accumulatedInt + len);

ここで、ストリームのタイプはreduceの戻り値のタイプと一致するため、reduceの2つのパラメーターバージョンを使用できます。

もちろん、reduceを使用する必要はまったくありません。

int length = asList("str1", "str2").stream().mapToInt (s -> s.length())
            .sum();
62
Eran

概念を明確にするために落書きと矢印が好きなので...始めましょう!

文字列から文字列へ(順次ストリーム)

4つの文字列があるとします。目標は、そのような文字列を1つに連結することです。基本的にはタイプで開始し、同じタイプで終了します。

あなたはこれを達成することができます

String res = Arrays.asList("one", "two","three","four")
        .stream()
        .reduce("",
                (accumulatedStr, str) -> accumulatedStr + str);  //accumulator

これにより、何が起こっているかを視覚化できます。

enter image description here

アキュムレータ関数は、(赤)ストリーム内の要素を段階的に変換して、最終的に削減された(緑)値に変換します。アキュムレータ関数は、単にStringオブジェクトを別のStringに変換します。

Stringからint(並列ストリーム)へ

同じ4つの文字列があるとします。新しい目標はそれらの長さを合計することであり、ストリームを並列化することです。

必要なのは次のようなものです:

int length = Arrays.asList("one", "two","three","four")
        .parallelStream()
        .reduce(0,
                (accumulatedInt, str) -> accumulatedInt + str.length(),                 //accumulator
                (accumulatedInt, accumulatedInt2) -> accumulatedInt + accumulatedInt2); //combiner

これは何が起こっているかのスキームです

enter image description here

ここで、アキュムレータ関数(BiFunction)を使用すると、Stringデータをintデータに変換できます。ストリームは並列であるため、2つの部分(赤)に分割されます。各部分は互いに独立して作成され、同様に多くの部分的な(オレンジ)結果が生成されます。部分的なintの結果を最終(緑色)intの1つにマージするためのルールを提供するには、コンバイナを定義する必要があります。

Stringからint(シーケンシャルストリーム)へ

ストリームを並列化したくない場合はどうしますか?とにかく、コンバイナを提供する必要がありますが、部分的な結果が生成されないため、コンバイナは呼び出されません。

94
Luigi Cortese

combinerなしで2つの異なるタイプを取るreduceバージョンはありません(なぜこれが要件であるかはわかりません)。 accumulatorが連想的でなければならないという事実により、このインターフェースはほとんど役に立たなくなります。

list.stream().reduce(identity,
                     accumulator,
                     combiner);

次と同じ結果を生成します。

list.stream().map(i -> accumulator(identity, i))
             .reduce(identity,
                     combiner);
0
quiz123