¿Cuál es la diferencia entre Collection.stream (). ForEach () y Collection.forEach ()?

Entiendo que con .stream() , puedo usar operaciones de cadena como .filter() o usar transmisión en paralelo. Pero, ¿cuál es la diferencia entre ellos si necesito ejecutar pequeñas operaciones (por ejemplo, imprimir los elementos de la lista)?

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

Para casos simples como el ilustrado, son en su mayoría iguales. Sin embargo, hay una serie de diferencias sutiles que pueden ser significativas.

Un problema es con ordenar. Con Stream.forEach , el orden no está definido . Es poco probable que ocurra con flujos secuenciales, aún así, está dentro de la especificación para que Stream.forEach ejecute en un orden arbitrario. Esto ocurre frecuentemente en transmisiones paralelas. Por el contrario, Iterable.forEach siempre se ejecuta en el orden de iteración de Iterable , si se especifica uno.

Otro problema es con los efectos secundarios. La acción especificada en Stream.forEach debe ser no interferente . (Consulte el documento del paquete java.util.stream .) Iterable.forEach tiene potencialmente menos restricciones. Para las colecciones en java.util , Iterable.forEach generalmente usará el Iterator esa colección, la mayoría de los cuales están diseñados para fallar y que lanzarán ConcurrentModificationException si la colección se modifica estructuralmente durante la iteración. Sin embargo, las modificaciones que no son estructurales se permiten durante la iteración. Por ejemplo, la documentación de la clase ArrayList dice “simplemente establecer el valor de un elemento no es una modificación estructural”. Por lo tanto, la acción de ArrayList.forEach permite establecer valores en ArrayList subyacente sin problemas.

Las colecciones concurrentes son una vez más diferentes. En lugar de fallar rápido, están diseñados para ser débilmente consistentes . La definición completa está en ese enlace. Sin embargo, brevemente, considere ConcurrentLinkedDeque . La acción pasó a su método. forEach método permite modificar el deque subyacente, incluso estructuralmente, y nunca se lanza ConcurrentModificationException . Sin embargo, la modificación que ocurre puede o no ser visible en esta iteración. (De ahí la consistencia “débil”)

Todavía otra diferencia es visible si Iterable.forEach está iterando sobre una colección sincronizada. En dicha colección, Iterable.forEach toma el locking de la colección una vez y lo mantiene en todas las llamadas al método de acción. La llamada Stream.forEach utiliza el Stream.forEach la colección, que no se bloquea, y que se basa en la regla predominante de no interferencia. La colección que respalda la secuencia podría modificarse durante la iteración, y si lo es, podría producirse una ConcurrentModificationException o comportamiento incoherente.

No hay diferencia entre los dos que ha mencionado, al menos conceptualmente, Collection.forEach() es solo una abreviatura.

Internamente, la versión de stream() tiene algo más de sobrecarga debido a la creación de objetos, pero al observar el tiempo de ejecución no tiene una sobrecarga.

Ambas implementaciones terminan iterando sobre el contenido de la collection una vez, y durante la iteración imprimen el elemento.

Esta respuesta se refiere al rendimiento de las diversas implementaciones de los bucles. Es solo marginalmente relevante para bucles que se llaman MUY FRECUENTES (como millones de llamadas). En la mayoría de los casos, el contenido del bucle será, con mucho, el elemento más caro. En situaciones en las que realiza bucles muy a menudo, esto podría ser de interés.

Debe repetir estas pruebas en el sistema de destino ya que esto es específico de la implementación ( código fuente completo ).

Ejecuto la versión 1.8.0_111 de openjdk en una máquina Linux rápida.

Escribí una prueba que gira 10 ^ 6 veces sobre una lista usando este código con diferentes tamaños para integers (10 ^ 0 -> 10 ^ 5 entradas).

Los resultados están debajo, el método más rápido varía según la cantidad de entradas en la lista.

Pero aún en las peores situaciones, pasar más de 10 ^ 5 entradas 10 ^ 6 veces tomó 100 segundos para el peor desempeño, por lo que otras consideraciones son más importantes en prácticamente todas las situaciones.

 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; }); } 

Aquí están mis tiempos: milisegundos / función / número de entradas en la lista. Cada carrera es 10 ^ 6 bucles.

  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 

Si repites el experimento, publiqué el código fuente completo . Edite esta respuesta y añada resultados con una notación del sistema probado.

 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, Intel Core i7 a 2,5 GHz, 16 GB, macOS 10.12.6)