24

Java 8 StreamAPI の設計を見直して、Stream.reduce()引数の一般的な不変性に驚きました。

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

同じ API の一見より用途の広いバージョンは、次のような への個々の参照に共分散/反分散を適用した可能性がありますU

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

これにより、現在不可能な次のことが可能になります。

// Assuming we want to reuse these tools all over the place:
BiFunction<Number, Number, Double> numberAdder =
    (t, u) -> t.doubleValue() + u.doubleValue();

// This currently doesn't work, but would work with the suggestion
Stream<Number> stream = Stream.of(1, 2L, 3.0);
double sum = stream.reduce(0.0, numberAdder, numberAdder);

回避策として、メソッド参照を使用して型をターゲット型に「強制」します。

double sum = stream.reduce(0.0, numberAdder::apply, numberAdder::apply);

C# には、次のように定義されているように、宣言サイトの差異を使用して、この特定の問題はありませんFunc(T1, T2, TResult)。つまり、API を使用するFuncと、この動作が無料で取得されます。

public delegate TResult Func<in T1, in T2, out TResult>(
    T1 arg1,
    T2 arg2
)

提案された設計に対する既存の設計の利点 (およびおそらく EG 決定の理由) は何ですか?

または、別の言い方をすれば、私が見落としている可能性のある提案された設計の注意点は何ですか (例: 型推論の難しさ、並列化の制約、または結合性などのリダクション操作に固有の制約、将来の Java の宣言サイトでの差異の予測BiFunction<in T, in U, out R>、 ...)?

4

2 に答える 2

12

ラムダ開発の歴史をたどり、この決定の「THE」理由を特定することは困難です。そのため、最終的には、開発者の 1 人がこの質問に答えるのを待つ必要があります。

いくつかのヒントは次のとおりです。

  • ストリーム インターフェイスは、数回の反復とリファクタリングを受けています。インターフェイスの最も初期のバージョンの 1 つには、Stream専用reduceのメソッドがあり、問題のメソッドに最も近いreduceメソッドが当時も呼び出されStream#foldていました。これはすでにパラメーターBinaryOperatorとしてa を受け取りました。combiner

  • 興味深いことに、かなり長い間、ラムダの提案には専用のインターフェースが含まれていましたCombiner<T,U,R>combiner直感に反して、これは関数内のとして使用されませんでしたStream#reduce。代わりに として使用され、reducer現在では と呼ばれているようaccumulatorです。ただし、Combinerインターフェースは後のリビジョンでに置き換えられましBiFunctionた。

  • ここでの質問との最も顕著な類似点は、メーリング リストの署名に関するスレッドStream#flatMapで見つかります。このスレッドは、ストリーム メソッドの署名の違いに関する一般的な質問に変わります。たとえば、いくつかの場所でこれらを修正しました

    ブライアンが私を訂正するように:

    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

    それ以外の:

    <R> Stream<R> flatMap(Function<T, Stream<? extends R>> mapper);

    しかし、いくつかの場所では、これが不可能であることに気付きました:

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

    Optional<T> reduce(BinaryOperator<T> accumulator);

    「BinaryOperator」を使用したため修正できませんが、「BiFunction」を使用すると、より柔軟になります

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

    それ以外の:

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

    「BinaryOperator」に関する同じコメント

    (私による強調)。


を aに置き換えないことについて私が見つけた唯一の正当化は、最終的に、同じスレッドで、このステートメントへの応答で与えられました。BinaryOperatorBiFunction

あなたが言ったように、BinaryOperatorはBiFunctionに置き換えられません.BinaryOperatorは、2つのパラメーターと戻り値の型が同じであることを要求するため、概念的により多くの重みを持ちます(EGはすでにそれに投票しています)。

誰かがこの決定を支配した専門家グループの投票の特定の参照を掘り起こすことができるかもしれませんが、おそらくこの引用は、なぜそれがそのようになっているのかという質問にすでに十分に答えています...

于 2016-02-28T17:03:50.960 に答える