Die Leistung hängt von so vielen Faktoren ab, dass es schwer vorherzusagen ist. Normalerweise würden wir sagen, wenn Ihr Vorgesetzter behauptet, dass es ein Problem mit der Leistung gab, ist Ihr Vorgesetzter dafür verantwortlich, zu erklären, welches Problem.
Eine Sache, vor der jemand Angst haben könnte, ist, dass hinter den Kulissen eine Klasse für jede Lambda-Erstellungsstelle (mit der aktuellen Implementierung) generiert wird. Wenn der betreffende Code nur einmal ausgeführt wird, kann dies als a betrachtet werden Verschwendung von Ressourcen. Dies stimmt mit der Tatsache überein, dass Lambda-Ausdrücke einen höheren Initialisierungsaufwand als der normale Imperativ-Code haben (wir vergleichen hier nicht mit inneren Klassen), so dass in Klassen-Initialisierern, die nur einmal ausgeführt werden, in Betracht gezogen wird, sie zu vermeiden. Dies entspricht auch der Tatsache, dass Sie never use parallel streams in class initializers sollten, damit dieser potenzielle Vorteil hier sowieso nicht zur Verfügung steht.
Für normalen, häufig ausgeführten Code, der wahrscheinlich von der JVM optimiert wird, treten diese Probleme nicht auf. Wie Sie richtig angenommen haben, erhalten Klassen, die für Lambda-Ausdrücke generiert wurden, die gleiche Behandlung (Optimierungen) wie andere Klassen. An diesen Orten kann forEach
auf Sammlungen effizienter als eine for
Schleife.
Die temporären Objektinstanzen für ein Iterator
oder den Lambda-Ausdruck erstellt sind vernachlässigbar, es könnte jedoch erwähnenswert, dass eine foreach-Schleife wird immer eine Iterator
Instanz während lambda expression do not always do erstellen. Während die default
Implementierung von Iterable.forEach
auch eine Iterator
erstellen wird, nutzen einige der am häufigsten verwendeten Sammlungen die Möglichkeit, eine spezialisierte Implementierung bereitzustellen, vor allem ArrayList
.
Die ArrayList
's forEach
ist im Grunde eine for
Schleife über ein Array, ohne Iterator
. Es wird dann die accept
-Methode der Consumer
aufrufen, die eine generierte Klasse sein wird, die eine triviale Delegation an die synthetische Methode enthält, die den Code Ihres Lambda-Ausdrucks enthält. Um die gesamte Schleife zu optimieren, muss der Horizont des Optimierers die Schleife ArrayList
über ein Array (ein allgemeines Idiom, das für einen Optimierer erkennbar ist) überspannen, die synthetische accept
Methode, die eine triviale Delegation und die Methode enthält, die Ihren tatsächlichen Code enthält.
Im Gegensatz dazu, wenn sie über die gleiche Liste Iterieren einer foreach-Schleife, eine Iterator
Implementierung wird geschaffen, um die ArrayList
Iterationslogik enthält, verteilt auf zwei Methoden, hasNext()
und next()
und Instanzvariablen des Iterator
. Die Schleife wird das Verfahren wiederholt aufrufen hasNext()
die Endbedingung (index<size
) und die next()
die Bedingung Bereiche nochmals zu überprüfen, bevor das Element zurückkehrt, da es keine Garantie ist, dass der Anrufer richtig hasNext()
vor next()
nicht aufrufen. Natürlich ist ein Optimierer in der Lage, diese Duplizierung zu entfernen, aber das erfordert mehr Aufwand als gar nicht. Um die gleiche Leistung wie die forEach
-Methode zu erhalten, muss der Horizont des Optimierers Ihren Schleifencode, die nicht-triviale hasNext()
Implementierung und die nicht-triviale next()
Implementierung umfassen.
Ähnliche Dinge können auch für andere Sammlungen mit einer spezialisierten forEach
Implementierung gelten. Dies gilt auch für Stream-Operationen, wenn die Quelle eine spezialisierte Spliterator
-Implementierung bereitstellt, die die Iterationslogik nicht über zwei Methoden wie Iterator
verteilt.
Also, wenn Sie die technischen Aspekte von for
jeweils vs forEach(…)
diskutieren möchten, können Sie diese Informationen verwenden.
Aber wie gesagt, beschreiben diese Aspekte nur mögliche Leistungsaspekte, da die Arbeit des Optimierers und andere Laufzeitumgebungsaspekte das Ergebnis vollständig verändern können. Ich denke, als Faustregel gilt, je kleiner der Schleifenkörper/die Aktion ist, desto geeigneter ist die forEach
Methode. Dies harmoniert perfekt mit der Vorgabe, zu lange Lambda-Ausdrücke zu vermeiden.
Parallele Streams können langsamer als normale Loops sein, wenn die CPU-Anforderungen und/oder die Stream-Größe nicht groß ist. Threads sind teuer zu verwalten. – Bohemian
Der Overhead des Stream-Frameworks kann überraschend sein. Messen! –
In der Tat und so habe ich meine Frage aktualisiert. – Konstantine