Eran Antwort beschrieben , die Unterschiede zwischen den beiden argument und drei argument Versionen reduce
, dass der ehemalige reduziert Stream<T>
auf T
während letztere reduziert Stream<T>
auf U
. Es wurde jedoch nicht die Notwendigkeit der zusätzlichen Kombiniererfunktion beim Reduzieren Stream<T>
auf erklärt U
.
Eines der Entwurfsprinzipien der Streams-API ist, dass sich die API nicht zwischen sequentiellen und parallelen Streams unterscheiden sollte, oder anders ausgedrückt, eine bestimmte API sollte nicht verhindern, dass ein Stream entweder sequentiell oder parallel korrekt ausgeführt wird. Wenn Ihre Lambdas die richtigen Eigenschaften haben (assoziativ, nicht störend usw.), sollte ein nacheinander oder parallel laufender Stream die gleichen Ergebnisse liefern.
Betrachten wir zunächst die Zwei-Argumente-Version der Reduktion:
T reduce(I, (T, T) -> T)
Die sequentielle Implementierung ist unkompliziert. Der Identitätswert I
wird mit dem nullten Stream-Element "akkumuliert", um ein Ergebnis zu erhalten. Dieses Ergebnis wird mit dem ersten Stream-Element akkumuliert, um ein anderes Ergebnis zu erhalten, das wiederum mit dem zweiten Stream-Element akkumuliert wird, und so weiter. Nachdem das letzte Element akkumuliert wurde, wird das Endergebnis zurückgegeben.
Die parallele Implementierung beginnt mit der Aufteilung des Streams in Segmente. Jedes Segment wird von seinem eigenen Thread in der oben beschriebenen sequentiellen Weise verarbeitet. Wenn wir nun N Threads haben, haben wir N Zwischenergebnisse. Diese müssen auf ein Ergebnis reduziert werden. Da jedes Zwischenergebnis vom Typ T ist und wir mehrere haben, können wir dieselbe Akkumulatorfunktion verwenden, um diese N Zwischenergebnisse auf ein einziges Ergebnis zu reduzieren.
Betrachten wir nun eine hypothetische Zwei-Arg-Reduktionsoperation, die sich Stream<T>
auf reduziert U
. In anderen Sprachen wird dies als a bezeichnet "Fold" - oder "Fold-Left" -Operation bezeichnet. So werde ich es hier nennen. Beachten Sie, dass dies in Java nicht vorhanden ist.
U foldLeft(I, (U, T) -> U)
(Beachten Sie, dass der Identitätswert I
vom Typ U ist.)
Die sequentielle Version von foldLeft
ist genau wie die sequentielle Version vonreduce
außer dass die Zwischenwerte vom Typ U anstelle vom Typ T sind. Ansonsten ist es dasselbe. (Eine hypothetische foldRight
Operation wäre ähnlich, außer dass die Operationen von rechts nach links statt von links nach rechts ausgeführt würden.)
Betrachten Sie nun die parallele Version von foldLeft
. Beginnen wir mit der Aufteilung des Streams in Segmente. Wir können dann jeden der N Threads die T-Werte in seinem Segment in N Zwischenwerte vom Typ U reduzieren lassen. Was nun? Wie kommen wir von N Werten vom Typ U zu einem einzelnen Ergebnis vom Typ U?
Was fehlt , ist eine weitere Funktion, die kombiniert die mehreren Zwischenergebnisse des Typs U zu einem einzigen Ergebnis vom Typ U. Wenn wir eine Funktion , die kombiniert zwei U - Werte in eine, die eine beliebige Anzahl von Werten nach unten auf einen reduzieren ausreichend ist - wie die ursprüngliche Reduktion oben. Daher benötigt die Reduktionsoperation, die ein Ergebnis eines anderen Typs ergibt, zwei Funktionen:
U reduce(I, (U, T) -> U, (U, U) -> U)
Oder mit Java-Syntax:
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
Zusammenfassend benötigen wir für eine parallele Reduktion auf einen anderen Ergebnistyp zwei Funktionen: eine, die T-Elemente auf mittlere U-Werte akkumuliert , und eine zweite, die die die U-Zwischenwerte zu einem einzigen U-Ergebnis kombiniert . Wenn wir nicht zwischen Typen wechseln, stellt sich heraus, dass die Akkumulatorfunktion mit der Kombiniererfunktion identisch ist. Aus diesem Grund hat die Reduktion auf denselben Typ nur die Akkumulatorfunktion, und die Reduktion auf einen anderen Typ erfordert separate Akkumulator- und Kombiniererfunktionen.
Schließlich ist Java nicht bieten foldLeft
und foldRight
Operationen , weil sie eine bestimmte Reihenfolge von Operationen bedeuten , die von Natur aus sequentiell ist. Dies steht im Widerspruch zu dem oben genannten Entwurfsprinzip, APIs bereitzustellen, die sequentiellen und parallelen Betrieb gleichermaßen unterstützen.