Para casos simples como el ilustrado, son en su mayoría los mismos. Sin embargo, hay una serie de diferencias sutiles que pueden ser significativas.
Un problema es con el pedido. Con Stream.forEach
, el orden no está definido . Es poco probable que ocurra con secuencias secuenciales, aún así, está dentro de la especificación para Stream.forEach
ejecutarse en algún orden arbitrario. Esto ocurre con frecuencia en flujos paralelos. Por el contrario, Iterable.forEach
siempre se ejecuta en el orden de iteración delIterable
, si se especifica uno.
Otro problema es con los efectos secundarios. Se Stream.forEach
requiere que la acción especificada en no interfiera . (Consulte el documento del paquete java.util.stream ). Iterable.forEach
Potencialmente tiene menos restricciones. Para las colecciones en java.util
, Iterable.forEach
generalmente se usará esa colección Iterator
, la mayoría de las cuales están diseñadas para ser a prueba de fallas y que se lanzarán ConcurrentModificationException
si la colección se modifica estructuralmente durante la iteración. Sin embargo, las modificaciones que no son estructurales están permitidas durante la iteración. Por ejemplo, la documentación de la clase ArrayList dice que "simplemente establecer el valor de un elemento no es una modificación estructural". Por lo tanto, la acción paraArrayList.forEach
puede establecer valores en el subyacente ArrayList
sin problemas.
Las colecciones concurrentes son una vez más diferentes. En lugar de fallar rápidamente, están diseñados para ser débilmente consistentes . La definición completa está en ese enlace. Brevemente, sin embargo, considere ConcurrentLinkedDeque
. La acción pasó a su forEach
método está permitido modificar el deque subyacente, incluso estructuralmente, y ConcurrentModificationException
no se lanza. Sin embargo, la modificación que se produce puede o no ser visible en esta iteración. (De ahí la consistencia "débil").
Aún hay otra diferencia visible si Iterable.forEach
está iterando sobre una colección sincronizada. En dicha colección, Iterable.forEach
toma el bloqueo de la colección una vez y lo retiene en todas las llamadas al método de acción. La Stream.forEach
llamada utiliza el spliterator de la colección, que no se bloquea y que se basa en la regla prevaleciente de no interferencia. La colección que respalda la secuencia podría modificarse durante la iteración, y si es así, ConcurrentModificationException
podría producirse un comportamiento inconsistente o no.
Iterable.forEach takes the collection's lock
. ¿De dónde es esta información? No puedo encontrar ese comportamiento en las fuentes JDK.ArrayList
tienen una comprobación bastante estricta de modificaciones concurrentes, y por lo tanto, a menudo se lanzaránConcurrentModificationException
. Pero esto no está garantizado, particularmente para flujos paralelos. En lugar de CME, puede obtener una respuesta inesperada. Considere también modificaciones no estructurales a la fuente de flujo. Para flujos paralelos, no sabe qué hilo procesará un elemento en particular, ni si se ha procesado en el momento en que se modifica. Esto establece una condición de carrera, donde puede obtener resultados diferentes en cada carrera y nunca obtener un CME.Esta respuesta se refiere al rendimiento de las diversas implementaciones de los bucles. Es solo marginalmente relevante para los bucles que se llaman MUY A MENUDO (como millones de llamadas). En la mayoría de los casos, el contenido del bucle será, con mucho, el elemento más costoso. Para situaciones en las que realizas bucles con mucha frecuencia, 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 openjdk versión 1.8.0_111 en una máquina Linux rápida.
Escribí una prueba que recorre 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 a continuación, 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, recorrer 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.
Aquí están mis tiempos: milisegundos / función / número de entradas en la lista. Cada ejecución es de 10 ^ 6 bucles.
Si repite el experimento, publiqué el código fuente completo . Edite esta respuesta y agregue sus resultados con una notación del sistema probado.
Usando una MacBook Pro, Intel Core i7 a 2.5 GHz, 16 GB, macOS 10.12.6:
Java 8 Hotspot VM - 3.4GHz Intel Xeon, 8 GB, Windows 10 Pro
Java 11 Hotspot VM - 3.4GHz Intel Xeon, 8 GB, Windows 10 Pro
(misma máquina que la anterior, versión diferente de JDK)
Java 11 OpenJ9 VM - 3.4GHz Intel Xeon, 8 GB, Windows 10 Pro
(misma máquina y versión JDK que la anterior, VM diferente)
Java 8 Hotspot VM - 2.8 GHz AMD, 64 GB, Windows Server 2016
Java 11 Hotspot VM - 2.8GHz AMD, 64 GB, Windows Server 2016
(misma máquina que la anterior, versión diferente de JDK)
Java 11 OpenJ9 VM - 2.8GHz AMD, 64 GB, Windows Server 2016
(misma máquina y versión JDK que la anterior, VM diferente)
La implementación de VM que elija también marca la diferencia Hotspot / OpenJ9 / etc.
fuente
No hay diferencia entre los dos que ha mencionado, al menos conceptualmente,
Collection.forEach()
es solo una abreviatura.Internamente, la
stream()
versión tiene algo más de sobrecarga debido a la creación de objetos, pero mirando el tiempo de ejecución tampoco tiene una sobrecarga allí.Ambas implementaciones terminan iterando sobre los
collection
contenidos una vez, y durante la iteración imprimen el elemento.fuente
Stream
creación o los objetos individuales? AFAIK, aStream
no duplica los elementos.Collection.forEach () usa el iterador de la colección (si se especifica uno). Eso significa que se define el orden de procesamiento de los artículos. Por el contrario, el orden de procesamiento de Collection.stream (). ForEach () no está definido.
En la mayoría de los casos, no importa cuál de los dos elegimos. Las secuencias paralelas nos permiten ejecutar la secuencia en múltiples subprocesos, y en tales situaciones, el orden de ejecución no está definido. Java solo requiere que todos los subprocesos finalicen antes de llamar a cualquier operación de terminal, como Collectors.toList (). Veamos un ejemplo donde primero llamamos a ForEach () directamente en la colección, y segundo, en una secuencia paralela:
Si ejecutamos el código varias veces, vemos que list.forEach () procesa los elementos en orden de inserción, mientras que list.parallelStream (). ForEach () produce un resultado diferente en cada ejecución. Una salida posible es:
Otro es:
fuente