Solución:
Para casos simples como el ilustrado, son casi iguales. Sin embargo, hay una serie de diferencias sutiles que pueden ser significativas.
Un problema es el pedido. Con Stream.forEach
, el orden es indefinido. Es poco probable que ocurra con transmisiones secuenciales, aún así, está dentro de la especificación para Stream.forEach
para ejecutar en algún orden arbitrario. Esto ocurre con frecuencia en corrientes paralelas. Por el contrario, Iterable.forEach
siempre se ejecuta en el orden de iteración del Iterable
, si se especifica uno.
Otro problema son los efectos secundarios. La acción especificada en Stream.forEach
se requiere ser no interfiriendo. (Consulte el documento del paquete java.util.stream). Iterable.forEach
potencialmente tiene menos restricciones. Para las colecciones en java.util
, Iterable.forEach
generalmente usará la colección Iterator
, la mayoría de las cuales están diseñadas para ser rápidas y que arrojarán ConcurrentModificationException
si la colección se modifica estructuralmente durante la iteración. Sin embargo, las modificaciones que no son estructurales están permitido 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 para ArrayList.forEach
se permite establecer valores en el subyacente ArrayList
sin problemas.
Las colecciones concurrentes son nuevamente diferentes. En lugar de ser rápidos, 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 es permitido modificar la deque subyacente, incluso estructuralmente, y ConcurrentModificationException
nunca se lanza. Sin embargo, la modificación que se produce puede ser visible o no en esta iteración. (De ahí la consistencia “débil”).
Aún otra diferencia es visible si Iterable.forEach
está iterando sobre una colección sincronizada. En tal colección, Iterable.forEach
toma el bloqueo de la colección una vez y lo mantiene en todas las llamadas al método de acción. los Stream.forEach
call usa el spliterator de la colección, que no se bloquea y que se basa en la regla imperante de no interferencia. La colección que respalda el flujo podría modificarse durante la iteración, y si es así, un ConcurrentModificationException
o podría resultar un comportamiento inconsistente.
Esta respuesta se refiere en sí misma al rendimiento de las diversas implementaciones de los bucles. Es solo marginalmente relevante para los 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. Para situaciones en las que haces bucles con mucha frecuencia, esto podría resultar de interés.
Debe repetir estas pruebas en el sistema de destino, ya que es una implementación específica (código fuente completo).
Ejecuto openjdk versión 1.8.0_111 en una máquina Linux rápida.
Escribí una prueba que se repite 10 ^ 6 veces en una lista usando este código con diferentes tamaños para integers
(10 ^ 0 -> 10 ^ 5 entradas).
Los resultados se muestran 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.
public int outside = 0;
private void iteratorForEach(List<Integer> integers) {
integers.forEach((ii) -> {
outside = ii*ii;
});
}
private void forEach(List<Integer> integers) {
for(Integer next : integers) {
outside = next * next;
}
}
private void forCounter(List<Integer> integers) {
for(int ii = 0; ii < integers.size(); ii++) {
Integer next = integers.get(ii);
outside = next*next;
}
}
private void iteratorStream(List<Integer> integers) {
integers.stream().forEach((ii) -> {
outside = ii*ii;
});
}
Aquí están mis tiempos: milisegundos / función / número de entradas en la lista. Cada ejecución es de 10 ^ 6 bucles.
1 10 100 1000 10000
iterator.forEach 27 116 959 8832 88958
for:each 53 171 1262 11164 111005
for with index 39 112 920 8577 89212
iterable.stream.forEach 255 324 1030 8519 88419
Si repite el experimento, publiqué el código fuente completo. Edite esta respuesta y agregue los resultados con una notación del sistema probado.
Con una MacBook Pro, Intel Core i7 de 2,5 GHz, 16 GB, macOS 10.12.6:
1 10 100 1000 10000
iterator.forEach 27 106 1047 8516 88044
for:each 46 143 1182 10548 101925
for with index 49 145 887 7614 81130
iterable.stream.forEach 393 397 1108 8908 88361
Java 8 Hotspot VM: Intel Xeon de 3,4 GHz, 8 GB, Windows 10 Pro
1 10 100 1000 10000
iterator.forEach 30 115 928 8384 85911
for:each 40 125 1166 10804 108006
for with index 30 120 956 8247 81116
iterable.stream.forEach 260 237 1020 8401 84883
Java 11 Hotspot VM: Intel Xeon de 3,4 GHz, 8 GB, Windows 10 Pro
(la misma máquina que la anterior, diferente versión de JDK)
1 10 100 1000 10000
iterator.forEach 20 104 940 8350 88918
for:each 50 140 991 8497 89873
for with index 37 140 945 8646 90402
iterable.stream.forEach 200 270 1054 8558 87449
Java 11 OpenJ9 VM: Intel Xeon de 3,4 GHz, 8 GB, Windows 10 Pro
(misma máquina y versión de JDK que la anterior, VM diferente)
1 10 100 1000 10000
iterator.forEach 211 475 3499 33631 336108
for:each 200 375 2793 27249 272590
for with index 384 467 2718 26036 261408
iterable.stream.forEach 515 714 3096 26320 262786
Java 8 Hotspot VM: AMD de 2,8 GHz, 64 GB, Windows Server 2016
1 10 100 1000 10000
iterator.forEach 95 192 2076 19269 198519
for:each 157 224 2492 25466 248494
for with index 140 368 2084 22294 207092
iterable.stream.forEach 946 687 2206 21697 238457
Java 11 Hotspot VM: AMD de 2,8 GHz, 64 GB, Windows Server 2016
(la misma máquina que la anterior, diferente versión de JDK)
1 10 100 1000 10000
iterator.forEach 72 269 1972 23157 229445
for:each 192 376 2114 24389 233544
for with index 165 424 2123 20853 220356
iterable.stream.forEach 921 660 2194 23840 204817
Java 11 OpenJ9 VM: AMD de 2,8 GHz, 64 GB, Windows Server 2016
(misma máquina y versión de JDK que la anterior, VM diferente)
1 10 100 1000 10000
iterator.forEach 592 914 7232 59062 529497
for:each 477 1576 14706 129724 1190001
for with index 893 838 7265 74045 842927
iterable.stream.forEach 1359 1782 11869 104427 958584
La implementación de VM que elija también marca la diferencia Hotspot / OpenJ9 / etc.
No hay diferencia entre los dos que ha mencionado, al menos conceptualmente, el Collection.forEach()
es solo una taquigrafía.
Internamente el stream()
La versión tiene algo más de sobrecarga debido a la creación de objetos, pero en cuanto al tiempo de ejecución, tampoco tiene una sobrecarga allí.
Ambas implementaciones terminan iterando sobre el collection
contenido una vez, y durante la iteración imprime el elemento.