Was ist der Unterschied zwischen Collection.stream (). ForEach () und Collection.forEach ()?

Ich verstehe, dass ich mit .stream() , kann ich .filter() wie. .filter() oder parallel Stream verwenden. Aber was ist der Unterschied zwischen ihnen, wenn ich kleine Operationen ausführen muss (zum Beispiel das Drucken der Elemente der Liste)?

 collection.stream().forEach(System.out::println); collection.forEach(System.out::println); 

Für einfache Fälle wie den dargestellten sind sie meistens gleich. Es gibt jedoch eine Reihe subtiler Unterschiede, die signifikant sein können.

Ein Problem ist mit der Bestellung. Bei Stream.forEach ist die Reihenfolge nicht definiert . Es ist unwahrscheinlich, dass es bei sequenziellen Streams auftritt, dennoch liegt es innerhalb der Spezifikation für Stream.forEach , das in beliebiger Reihenfolge ausgeführt wird. Dies geschieht häufig in parallelen Streams. Im Gegensatz dazu wird Iterable.forEach immer in der Iterationsreihenfolge des Iterable , wenn eines angegeben ist.

Ein anderes Problem ist mit Nebenwirkungen. Die in Stream.forEach angegebene Aktion muss nicht störend sein . (Siehe das Dokumentationspaket java.util.stream .) Iterable.forEach möglicherweise weniger Einschränkungen. Für die Auflistungen in java.util Iterable.forEach im Allgemeinen den Iterator dieser Auflistung, von denen die meisten für Fail-Fast ausgelegt sind und die ConcurrentModificationException wenn die Auflistung während der Iteration strukturell geändert wird. Änderungen, die nicht strukturell sind , sind jedoch während der Iteration zulässig. Zum Beispiel sagt die Dokumentation der ArrayList-class, dass “das bloße Setzen des Wertes eines Elements keine strukturelle Änderung ist”. Daher kann die Aktion für ArrayList.forEach Werte in der zugrunde liegenden ArrayList ohne Probleme festlegen.

Die konkurrierenden Sammlungen sind wieder anders. Statt Fail-Fast sind sie schwach konsistent . Die vollständige Definition ist bei diesem Link. Betrachten ConcurrentLinkedDeque kurz ConcurrentLinkedDeque . Die Aktion, die an ihre forEach Methode übergeben wird, darf die zugrunde liegende Deque auch strukturell ändern, und ConcurrentModificationException wird niemals ausgetriggers. Die Änderung, die auftritt, ist jedoch möglicherweise in dieser Iteration nicht sichtbar. (Daher die “schwache” Konsistenz.)

Noch ein weiterer Unterschied ist sichtbar, wenn Iterable.forEach über eine synchronisierte Sammlung iteriert. Bei einer solchen Sammlung übernimmt Iterable.forEach die Sperre der Sammlung einmal und hält sie über alle Aufrufe der Aktionsmethode hinweg fest. Der Stream.forEach Aufruf verwendet den Stream.forEach der Sammlung, der nicht gesperrt wird und der auf der vorherrschenden Regel der Nicht-Interferenz beruht. Die Sammlung, die den Stream unterstützt, könnte während der Iteration geändert werden, und wenn dies der Fall ist, kann eine ConcurrentModificationException oder ein inkonsistentes Verhalten die Folge sein.

Es gibt keinen Unterschied zwischen den beiden, die Sie erwähnt haben, zumindest konzeptionell ist die Collection.forEach() nur eine Kurzform.

Intern hat die stream() Version aufgrund der Objekterstellung etwas mehr Overhead, aber wenn man die Laufzeit betrachtet, hat sie dort keinen Overhead.

Beide Implementierungen enden damit, einmal über den collection iterieren und während der Iteration das Element auszudrucken.

Diese Antwort betrifft die performance der verschiedenen Implementierungen der Schleifen. Es ist nur marginal relevant für Schleifen, die SEHR OFTEN genannt werden (wie Millionen von Aufrufen). In den meisten Fällen ist der Inhalt der Schleife bei weitem das teuerste Element. Für Situationen, in denen Sie sich sehr oft wiederholen, könnte dies immer noch von Interesse sein.

Sie sollten diese Tests unter dem Zielsystem wiederholen, da dies implementierungsspezifisch ist ( vollständiger Quellcode ).

Ich starte openjdk Version 1.8.0_111 auf einem schnellen Linux-Rechner.

Ich schrieb einen Test, der 10 ^ 6 mal über eine Liste looping mit diesem Code mit unterschiedlichen Größen für integers (10 ^ 0 -> 10 ^ 5 Einträge).

Die Ergebnisse sind unten, die schnellste Methode hängt von der Anzahl der Einträge in der Liste ab.

Aber immer noch in den schlimmsten Situationen, dauerte das Durchlaufen von 10 ^ 5 Einträgen 10 ^ 6 Mal 100 Sekunden für den schlechtesten Spieler, so dass andere Überlegungen in fast allen Situationen wichtiger sind.

 public int outside = 0; private void forCounter(List integers) { for(int ii = 0; ii < integers.size(); ii++) { Integer next = integers.get(ii); outside = next*next; } } private void forEach(List integers) { for(Integer next : integers) { outside = next * next; } } private void iteratorForEach(List integers) { integers.forEach((ii) -> { outside = ii*ii; }); } private void iteratorStream(List integers) { integers.stream().forEach((ii) -> { outside = ii*ii; }); } 

Hier sind meine Zeiten: Millisekunden / function / Anzahl der Einträge in der Liste. Jeder Lauf ist 10 ^ 6 Schleifen.

  1 10 100 1000 10000 for with index 39 112 920 8577 89212 iterator.forEach 27 116 959 8832 88958 for:each 53 171 1262 11164 111005 iterable.stream.forEach 255 324 1030 8519 88419 

Wenn Sie das Experiment wiederholen, habe ich den vollständigen Quellcode veröffentlicht . Bitte editieren Sie diese Antwort und fügen Sie Ergebnisse mit einer Notation des getesteten Systems hinzu.

 I got: 1 10 100 1000 10000 iterator.forEach 27 106 1047 8516 88044 for:each 46 143 1182 10548 101925 iterable.stream.forEach 393 397 1108 8908 88361 for with index 49 145 887 7614 81130 

(MacBook Pro, 2,5 GHz Intel Core i7, 16 GB, macOS 10.12.6)