Für einfache Fälle wie den abgebildeten sind sie meistens gleich. Es gibt jedoch eine Reihe subtiler Unterschiede, die erheblich sein können.
Ein Problem ist die Bestellung. Mit Stream.forEach
ist die Reihenfolge undefiniert . Es ist unwahrscheinlich, dass es bei sequentiellen Streams auftritt, dennoch liegt es innerhalb der Spezifikation für Stream.forEach
die Ausführung in einer beliebigen Reihenfolge. Dies tritt häufig in parallelen Streams auf. Im Gegensatz dazu Iterable.forEach
wird immer in der Iterationsreihenfolge von ausgeführt Iterable
, wenn eine angegeben ist.
Ein weiteres Problem sind Nebenwirkungen. Die angegebene Aktion in Stream.forEach
erforderlich sein , nicht störenden . (Siehe das Dokument java.util.stream-Paket .) Iterable.forEach
Möglicherweise gibt es weniger Einschränkungen. Bei den Sammlungen im java.util
, Iterable.forEach
wird in der Regel , dass die Sammlung verwenden Iterator
, von denen die meisten sind so konzipiert , um ausfall schnell und die werfen , ConcurrentModificationException
wenn die Sammlung strukturell während der Iteration modifiziert wird. Änderungen, die nicht strukturell sind , sind jedoch während der Iteration zulässig. In der Dokumentation zur ArrayList-Klasse heißt es beispielsweise : "Das bloße Festlegen des Werts eines Elements ist keine strukturelle Änderung." Somit ist die Aktion fürArrayList.forEach
darf ArrayList
problemlos Werte im Basiswert setzen .
Die gleichzeitigen Sammlungen sind wieder anders. Anstatt ausfallsicher zu sein, sind sie so konzipiert, dass sie schwach konsistent sind . Die vollständige Definition finden Sie unter diesem Link. Überlegen Sie jedoch kurz ConcurrentLinkedDeque
. Die Aktion , die seine übergeben forEach
Methode ist erlaubt das darunter liegende deque zu ändern, auch strukturell und ConcurrentModificationException
wird nie geworfen. Die vorgenommene Änderung kann jedoch in dieser Iteration sichtbar sein oder nicht. (Daher die "schwache" Konsistenz.)
Ein weiterer Unterschied ist sichtbar, wenn Iterable.forEach
eine synchronisierte Sammlung durchlaufen wird. Nimmt bei einer solchen Iterable.forEach
Sammlung die Sperre der Sammlung einmal und hält sie für alle Aufrufe der Aktionsmethode. Der Stream.forEach
Aufruf verwendet den Spliterator der Sammlung, der nicht sperrt und auf der geltenden Regel der Nichteinmischung beruht. Die Sammlung, die den Stream unterstützt, kann während der Iteration geändert werden. Andernfalls kann es zu einem ConcurrentModificationException
oder inkonsistenten Verhalten kommen.
Iterable.forEach takes the collection's lock
. Woher kommen diese Informationen? Ich kann ein solches Verhalten in JDK-Quellen nicht finden.ArrayList
eine ziemlich strenge Überprüfung auf gleichzeitige Änderungen, werden daher häufig geworfenConcurrentModificationException
. Dies ist jedoch nicht garantiert, insbesondere für parallele Streams. Anstelle von CME erhalten Sie möglicherweise eine unerwartete Antwort. Berücksichtigen Sie auch nicht strukturelle Änderungen an der Stream-Quelle. Bei parallelen Streams wissen Sie nicht, welcher Thread ein bestimmtes Element verarbeitet oder ob es zum Zeitpunkt der Änderung verarbeitet wurde. Dadurch wird eine Rennbedingung festgelegt, bei der Sie bei jedem Lauf unterschiedliche Ergebnisse erzielen und niemals ein CME erhalten.Diese Antwort befasst sich mit der Leistung der verschiedenen Implementierungen der Schleifen. Es ist nur unwesentlich relevant für Schleifen, die SEHR OFT genannt werden (wie Millionen von Anrufen). In den meisten Fällen ist der Inhalt der Schleife bei weitem das teuerste Element. In Situationen, in denen Sie sehr oft eine Schleife ausführen, ist dies möglicherweise immer noch von Interesse.
Sie sollten diese Tests unter dem Zielsystem wiederholen, da dies implementierungsspezifisch ist ( vollständiger Quellcode ).
Ich führe openjdk Version 1.8.0_111 auf einem schnellen Linux-Computer aus.
Ich habe einen Test geschrieben, der 10 ^ 6 Mal eine Liste durchläuft, wobei dieser Code mit unterschiedlichen Größen für
integers
(10 ^ 0 -> 10 ^ 5 Einträge) verwendet wird.Die Ergebnisse sind unten aufgeführt. Die schnellste Methode hängt von der Anzahl der Einträge in der Liste ab.
Aber immer noch in den schlimmsten Situationen dauerte das Schleifen über 10 ^ 5 Einträge 10 ^ 6 Mal 100 Sekunden für den schlechtesten Darsteller, sodass andere Überlegungen in praktisch allen Situationen wichtiger sind.
Hier sind meine Timings: Millisekunden / Funktion / Anzahl der Einträge in der Liste. Jeder Lauf besteht aus 10 ^ 6 Schleifen.
Wenn Sie das Experiment wiederholen, habe ich den vollständigen Quellcode veröffentlicht . Bitte bearbeiten Sie diese Antwort und fügen Sie Ihre Ergebnisse mit einer Notation des getesteten Systems hinzu.
Verwenden eines MacBook Pro, 2,5 GHz Intel Core i7, 16 GB, macOS 10.12.6:
Java 8 Hotspot VM - 3,4 GHz Intel Xeon, 8 GB, Windows 10 Pro
Java 11 Hotspot VM - 3,4 GHz Intel Xeon, 8 GB, Windows 10 Pro
(gleicher Computer wie oben, andere JDK-Version)
Java 11 OpenJ9 VM - 3,4 GHz Intel Xeon, 8 GB, Windows 10 Pro
(gleicher Computer und JDK-Version wie oben, unterschiedliche VM)
Java 8 Hotspot VM - 2,8 GHz AMD, 64 GB, Windows Server 2016
Java 11 Hotspot VM - 2,8 GHz AMD, 64 GB, Windows Server 2016
(gleicher Computer wie oben, andere JDK-Version)
Java 11 OpenJ9 VM - 2,8 GHz AMD, 64 GB, Windows Server 2016
(gleiche Maschine und JDK-Version wie oben, unterschiedliche VM)
Die von Ihnen ausgewählte VM-Implementierung macht auch einen Unterschied. Hotspot / OpenJ9 / etc.
quelle
Es gibt keinen Unterschied zwischen den beiden, die Sie erwähnt haben, zumindest konzeptionell, das
Collection.forEach()
ist nur eine Abkürzung.Intern hat die
stream()
Version aufgrund der Objekterstellung etwas mehr Overhead, aber in Bezug auf die Laufzeit hat sie dort auch keinen Overhead.Beide Implementierungen iterieren einmal über den
collection
Inhalt und drucken während der Iteration das Element aus.quelle
Stream
oder auf die einzelnen Objekte? AFAIK, aStream
dupliziert die Elemente nicht.Collection.forEach () verwendet den Iterator der Sammlung (falls einer angegeben ist). Das heißt, die Verarbeitungsreihenfolge der Artikel ist definiert. Im Gegensatz dazu ist die Verarbeitungsreihenfolge von Collection.stream (). ForEach () undefiniert.
In den meisten Fällen spielt es keine Rolle, welche der beiden wir wählen. Parallele Streams ermöglichen es uns, den Stream in mehreren Threads auszuführen, und in solchen Situationen ist die Ausführungsreihenfolge undefiniert. In Java müssen nur alle Threads beendet sein, bevor eine Terminaloperation wie Collectors.toList () aufgerufen wird. Schauen wir uns ein Beispiel an, in dem wir erstens forEach () direkt in der Sammlung und zweitens in einem parallelen Stream aufrufen:
Wenn wir den Code mehrmals ausführen, sehen wir, dass list.forEach () die Elemente in Einfügereihenfolge verarbeitet, während list.parallelStream (). ForEach () bei jedem Lauf ein anderes Ergebnis erzeugt. Eine mögliche Ausgabe ist:
Ein anderer ist:
quelle