Considere una List<String> stringList
que se puede imprimir de muchas maneras usando construcciones Java 8 :
stringList.forEach(System.out::println); // 1) Iterable.forEach
stringList.stream().forEach(System.out::println); // 2) Stream.forEach (order maintained generally but doc does not guarantee)
stringList.stream().forEachOrdered(System.out::println); // 3) Stream.forEachOrdered (order maintained always)
stringList.parallelStream().forEach(System.out::println); // 4) Parallel version of Stream.forEach (order not maintained)
stringList.parallelStream().forEachOrdered(System.out::println); // 5) Parallel version ofStream.forEachOrdered (order maintained always)
¿En qué se diferencian estos enfoques entre sí?
Primer enfoque ( Iterable.forEach
):
el iterador de la colección generalmente se usa y está diseñado para fallar rápidamente, lo que significa que se lanzará ConcurrentModificationException
si la colección subyacente se modifica estructuralmente durante la iteración. Como se menciona en el documento para ArrayList
:
Una modificación estructural es cualquier operación que agrega o elimina uno o más elementos, o cambia el tamaño explícitamente de la matriz de respaldo; simplemente establecer el valor de un elemento no es una modificación estructural.
Por lo tanto, significa ArrayList.forEach
que el valor está permitido sin ningún problema. Y en el caso de una recopilación simultánea, por ejemplo, ConcurrentLinkedQueue
el iterador sería débilmente consistente, lo que significa que las acciones aprobadas forEach
pueden realizar incluso cambios estructurales sin que se produzca una ConcurrentModificationException
excepción. Pero aquí las modificaciones pueden o no ser visibles en esa iteración.
Segundo enfoque ( Stream.forEach
):
el orden no está definido. Aunque puede que no ocurra para secuencias secuenciales, la especificación no lo garantiza. También se requiere que la acción sea de naturaleza no interferente. Como se menciona en el documento :
El comportamiento de esta operación es explícitamente no determinista. Para las tuberías de flujo paralelo, esta operación no garantiza el respeto del orden de encuentro del flujo, ya que hacerlo sacrificaría el beneficio del paralelismo.
Tercer enfoque ( Stream.forEachOrdered
):
la acción se realizaría en el orden de encuentro de la secuencia. Por lo tanto, cuando el orden importa se usa forEachOrdered
sin pensarlo dos veces. Como se menciona en el documento :
Realiza una acción para cada elemento de esta secuencia, en el orden de encuentro de la secuencia si la secuencia tiene un orden de encuentro definido.
Mientras itera sobre una colección sincronizada, el primer enfoque tomaría el bloqueo de la colección una vez y lo mantendría en todos los métodos de llamadas a la acción, pero en el caso de las transmisiones, usan el spliterator de la colección, que no se bloquea y se basa en las reglas establecidas de no -interferencia. En caso de que la copia de respaldo de la secuencia se modifique durante la iteración, unConcurrentModificationException
, se generará un resultado incoherente.
Cuarto enfoque (paralelo Stream.forEach
):
como ya se mencionó, no hay garantía de respetar el orden de encuentro como se esperaba en el caso de flujos paralelos. Es posible que la acción se realice en diferentes hilos para diferentes elementos que nunca pueden ser el caso forEachOrdered
.
Quinta Approach (Parallel Stream.forEachOrdered
) -
El forEachOrdered
procesará los elementos en el orden especificado por la fuente con independencia del hecho de si la corriente es secuencial o en paralelo. Por lo tanto, no tiene sentido usar esto con flujos paralelos.
List
ser? Muéstranos cómo lo declaraste y lo instanciaste.