Ich habe eine Liste, myListToParse
in der ich die Elemente filtern, eine Methode auf jedes Element anwenden und das Ergebnis in eine andere Liste einfügen möchte myFinalList
.
Mit Java 8 habe ich festgestellt, dass ich das auf zwei verschiedene Arten tun kann. Ich möchte den effizienteren Weg zwischen ihnen kennen und verstehen, warum ein Weg besser ist als der andere.
Ich bin offen für Vorschläge zu einem dritten Weg.
Methode 1:
myFinalList = new ArrayList<>();
myListToParse.stream()
.filter(elt -> elt != null)
.forEach(elt -> myFinalList.add(doSomething(elt)));
Methode 2:
myFinalList = myListToParse.stream()
.filter(elt -> elt != null)
.map(elt -> doSomething(elt))
.collect(Collectors.toList());
java
java-8
java-stream
Emilien Brigand
quelle
quelle
elt -> elt != null
kann aber ersetzt werden durchObjects::nonNull
Optional<T>
stattdessen in Kombination mit zu verwendenflatMap
..map(this::doSomething)
Annahme schreiben können, dassdoSomething
es sich um eine nicht statische Methode handelt. Wenn es statisch ist, können Sie es durchthis
den Klassennamen ersetzen .Antworten:
Machen Sie sich keine Sorgen über Leistungsunterschiede, diese sind in diesem Fall normalerweise minimal.
Methode 2 ist vorzuziehen, weil
Es ist nicht erforderlich, eine Sammlung zu mutieren, die außerhalb des Lambda-Ausdrucks existiert.
Es ist besser lesbar, da die verschiedenen Schritte, die in der Erfassungspipeline ausgeführt werden, nacheinander geschrieben werden: zuerst eine Filteroperation, dann eine Kartenoperation und dann das Erfassen des Ergebnisses (weitere Informationen zu den Vorteilen von Erfassungspipelines finden Sie in Martin Fowlers ausgezeichnetem Artikel ).
Sie können die Art und Weise, wie Werte erfasst werden, leicht ändern, indem Sie die ersetzen
Collector
verwendeten . In einigen Fällen müssen Sie möglicherweise Ihre eigenen schreibenCollector
, aber der Vorteil ist, dass Sie diese problemlos wiederverwenden können.quelle
Ich stimme den vorhandenen Antworten zu, dass die zweite Form besser ist, weil sie keine Nebenwirkungen hat und einfacher zu parallelisieren ist (verwenden Sie einfach einen parallelen Stream).
In Bezug auf die Leistung scheinen sie gleichwertig zu sein, bis Sie parallele Streams verwenden. In diesem Fall ist die Leistung der Karte sehr viel besser. Siehe unten die Ergebnisse des Mikro-Benchmarks :
Sie können das erste Beispiel nicht auf die gleiche Weise verbessern , da forEach eine Terminalmethode ist - es gibt void zurück - und Sie gezwungen sind, ein statusbehaftetes Lambda zu verwenden. Aber das ist wirklich eine schlechte Idee, wenn Sie parallele Streams verwenden .
Beachten Sie schließlich, dass Ihr zweites Snippet mit Methodenreferenzen und statischen Importen etwas präziser geschrieben werden kann:
quelle
Einer der Hauptvorteile der Verwendung von Streams besteht darin, dass Daten deklarativ verarbeitet werden können, dh mithilfe eines funktionalen Programmierstils. Es bietet auch kostenlose Multithreading-Funktionen, sodass kein zusätzlicher Multithreading-Code geschrieben werden muss, damit Ihr Stream gleichzeitig ausgeführt wird.
Angenommen, Sie untersuchen diesen Programmierstil, weil Sie diese Vorteile nutzen möchten, dann ist Ihr erstes Codebeispiel möglicherweise nicht funktionsfähig, da die
foreach
Methode als terminal eingestuft wird (was bedeutet, dass sie Nebenwirkungen hervorrufen kann).Der zweite Weg wird aus Sicht der funktionalen Programmierung bevorzugt, da die Kartenfunktion zustandslose Lambda-Funktionen akzeptieren kann. Genauer gesagt sollte das an die Kartenfunktion übergebene Lambda sein
ArrayList
. B. ).Ein weiterer Vorteil des zweiten Ansatzes besteht darin, dass, wenn der Strom parallel ist und der Kollektor gleichzeitig und ungeordnet ist, diese Eigenschaften nützliche Hinweise für die Reduktionsoperation liefern können, um das Sammeln gleichzeitig durchzuführen.
quelle
Wenn Sie Eclipse-Sammlungen verwenden , können Sie die
collectIf()
Methode verwenden.Es wird eifrig ausgewertet und sollte etwas schneller sein als die Verwendung eines Streams.
Hinweis: Ich bin ein Committer für Eclipse-Sammlungen.
quelle
Ich bevorzuge den zweiten Weg.
Wenn Sie auf die erste Weise einen parallelen Stream verwenden, um die Leistung zu verbessern, haben Sie keine Kontrolle über die Reihenfolge, in der die Elemente der Ausgabeliste von hinzugefügt werden
forEach
.Bei Verwendung
toList
behält die Streams-API die Reihenfolge bei, auch wenn Sie einen parallelen Stream verwenden.quelle
forEachOrdered
anstattforEach
einen parallelen Stream zu verwenden, aber dennoch die Reihenfolge beizubehalten. Aber als Dokumentation fürforEach
Staaten opfert die Beibehaltung der Begegnungsreihenfolge den Vorteil der Parallelität. Ich vermute, dass dies auch dann der FalltoList
ist.Es gibt eine dritte Option - Verwenden
stream().toArray()
- siehe Kommentare unter Warum kein Stream eine toList-Methode hatte . Es ist langsamer als forEach () oder collect () und weniger ausdrucksstark. Es könnte in späteren JDK-Builds optimiert werden, also fügen Sie es hier für alle Fälle hinzu.unter der Annahme
List<String>
mit einem Mikro-Mikro-Benchmark, 1 Million Einträgen, 20% Nullen und einer einfachen Transformation in doSomething ()
Die Ergebnisse sind
parallel:
sequentiell:
parallel ohne Nullen und Filter (so ist der Stream
SIZED
): toArrays hat in diesem Fall die beste Leistung und.forEach()
schlägt mit "indexOutOfBounds" auf der Empfänger-ArrayList fehl, die durch ersetzt werden musste.forEachOrdered()
quelle
Kann Methode 3 sein.
Ich ziehe es immer vor, die Logik getrennt zu halten.
quelle
Wenn die Verwendung von 3rd Pary Libaries in Ordnung ist, definiert cyclops-react Lazy Extended Collections mit dieser integrierten Funktionalität. Zum Beispiel könnten wir einfach schreiben
ListX myListToParse;
ListX myFinalList = myListToParse.filter (elt -> elt! = Null) .map (elt -> doSomething (elt));
myFinalList wird erst beim ersten Zugriff ausgewertet (und dort, nachdem die materialisierte Liste zwischengespeichert und wiederverwendet wurde).
[Offenlegung Ich bin der Hauptentwickler von Cyclops-React]
quelle