Ersetzt traditionelles newForLoop durch Java 8 Streams

8

Nachdem ich nun einen relativ großen Sprung von Java 6 zu Java 8 gemacht habe, habe ich eine Menge Java 8 Streams API gelesen. Leider sind fast alle Beispiele, die ich gestellt habe, fast so ähnlich, wie ich es herausfinden möchte, aber nicht nahe genug.

Was ich habe ist

%Vor%

Nun habe ich verstanden, dass ich etwas Ähnliches mit .stream().forEach() machen könnte, aber das gilt nur für die foreach und die Streams erfordern finale Variablen. Ich habe versucht, ein wenig mit DoubleStream zu erkunden, um ein sum() zu erhalten, aber ich müsste die aktuelle Summe auf jedes Function anwenden und diese Summe zur nächsten Funktion hinzufügen, wie im obigen Codebeispiel zeigt.

Ist das mit der Pure Stream API möglich?

Edit: Nach dem Testen mit dem reduce() -Bereich habe ich einen einfachen Test über die Zeit durchgeführt, die benötigt wird, um diese Art von Berechnung durchzuführen und die Ergebnisse sind nicht für Ströme. Hier ist ein Beispiel Ссылка . Enthalten sind die Log-Ausgaben des ziemlich einfachen Tests.

    
gabizou 01.09.2015, 18:27
quelle

3 Antworten

10

Sie können die Stream-API verwenden, um eine Funktion aus Ihrer Funktionsliste zusammenzustellen.

%Vor%

Die Stream-Operation, die die komponierte Funktion erzeugt, hat kein Assoziativitätsproblem und könnte theoretisch sogar parallel laufen (obwohl es hier keinen Vorteil gibt), während das Ergebnis eine einzelne Funktion ist, die per se sequentiell ist und nie in Teile aufgelöst wird Das müsste assoziativ sein.

    
Holger 02.09.2015, 08:06
quelle
3

Ja, Sie können eine Stream-Lösung verwenden, indem Sie eine Reduktion durchführen:

%Vor%

Eine Reduktion nimmt jedes Element und aggregiert (reduziert) es auf einen Wert. Es gibt 3 Varianten der reduce() -Methode - die hier verwendete Methode macht den Trick.

Einige Testcodes:

%Vor%

Ausgabe:

%Vor%     
Bohemian 01.09.2015 19:06
quelle
0

Dieses spezielle Beispiel ist sehr problematisch für die Java 8-Streams. Sie sind für Operationen konzipiert, bei denen die Reihenfolge nicht wichtig ist.

Die Funktionsanwendung ist nicht assoziativ. Um es zu erklären, nehmen wir ein einfacheres Beispiel, in dem man eine Zahl nehmen und sie durch eine Zahlenliste teilen möchte:

%Vor%

Also, was du bekommst, ist

%Vor%

Die Arithmetik ist einfach - Division ist linksassoziativ, was bedeutet, dass dies äquivalent zu

ist %Vor%

nicht

%Vor%

und nicht

%Vor%

Die Stream-Operationen, die auf Reduzieren / Sammeln basieren, erfordern, dass die "Reduzieren" -Operation linksassoziativ ist. Dies liegt daran, dass die Operation parallelisiert werden soll, sodass einige Threads einige der Operationen ausführen und das Ergebnis dann kombiniert werden kann. Nun, wenn unser Operator Multiplikation statt Division wäre, wäre das kein Problem, denn

%Vor%

ist dasselbe wie

%Vor%

bedeutet, dass ein Thread das a × 3.5 × 7.0 ausführen kann und ein anderes die 0.5 × 19.0 -Operation, und dann können Sie das Ergebnis multiplizieren und dasselbe wie in der sequenziellen Berechnung erhalten. Aber für die Teilung funktioniert das nicht.

Funktion Anwendung ist auch nicht assoziativ, genau wie Division. Das heißt, wenn Sie die Funktionen f , g und h haben und Ihre sequenzielle Berechnung ausführen, erhalten Sie:

%Vor%

Wenn Sie jetzt zwei Zwischen-Threads haben, wobei einer f und g anwendet und der andere h anwendet, und Sie das Ergebnis kombinieren möchten - es gibt keine Möglichkeit, die korrekten Werte in% zu erhalten. co_de% an erster Stelle.

Sie könnten versucht sein, dies mit einer Methode wie h zu versuchen, wie @Bohemian vorgeschlagen hat. Aber die Dokumentation warnt Sie davor:

%Vor%      

...

     

Der Identitätswert muss eine Identität für die Kombinatorfunktion sein. Dies bedeutet, dass für alle u, Combiner (Identität, u) gleich u ist. Zusätzlich muss die Kombinatorfunktion mit der Akkumulatorfunktion kompatibel sein; Für alle u und t muss folgendes gelten:

%Vor%

Für eine Operation wie Stream.reduce ist die Identität 0. Für + ist die Identität 1. Es ist also gegen die Dokumentation, Ihre * als val zu verwenden. Und die zweite Bedingung ist noch problematischer.

Obwohl die aktuelle Implementierung für einen nicht parallelen Stream nicht den Combiner -Teil verwendet, wodurch beide Bedingungen nicht benötigt werden, ist dies nicht dokumentiert strong> und eine zukünftige Implementierung oder eine andere JRE-Implementierung können sich dazu entschließen, Zwischenergebnisse zu erstellen und den Kombinierer zu verwenden, um die Leistung zu verbessern oder andere Überlegungen anzustellen.

Trotz der Versuchung sollte man nicht identity verwenden, um zu versuchen, die ursprüngliche sequentielle Verarbeitung zu imitieren.

There ist ein Weg dies zu tun, der die Dokumentation nicht bricht. Es beinhaltet das Beibehalten eines veränderbaren Objekts, das das Ergebnis enthält (es muss ein Objekt sein, damit es effektiv final ist, während es noch veränderbar ist), und das Verwenden des Stream.reduce , was garantiert, dass die Operationen in der Reihenfolge ausgeführt werden, in der sie im Stream erscheinen, wenn der Stream geordnet ist. Und der Stream einer Liste hat eine definierte Reihenfolge. Dies funktioniert auch, wenn Sie Stream.forEachOrdered verwenden.

%Vor%

Ich persönlich finde, dass Ihre Originalschleife besser lesbar ist, also gibt es wirklich keinen Vorteil, Streams hier zu verwenden.

Basierend auf einem Kommentar von @Tagir Valeev gibt es eine foldLeft Operation, die für zukünftige Java-Versionen geplant ist . Es kann eleganter aussehen, wenn das passiert.

    
RealSkeptic 02.09.2015 07:46
quelle