Considere um List<String> stringList
que pode ser impresso de várias maneiras usando construções 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)
Como essas abordagens são diferentes umas das outras?
Primeira abordagem ( Iterable.forEach
) -
o iterador da coleção geralmente é usado e é projetado para ser rápido, o que significa que será lançado ConcurrentModificationException
se a coleção subjacente for estruturalmente modificada durante a iteração. Conforme mencionado no documento para ArrayList
:
Uma modificação estrutural é qualquer operação que adiciona ou exclui um ou mais elementos ou redimensiona explicitamente a matriz de suporte; apenas definir o valor de um elemento não é uma modificação estrutural.
Então, isso significa ArrayList.forEach
que a definição do valor é permitida sem nenhum problema. E no caso de coleta simultânea, por exemplo, ConcurrentLinkedQueue
o iterador seria fracamente consistente, o que significa que as ações transmitidas forEach
podem fazer alterações estruturais mesmo sem que ConcurrentModificationException
sejam lançadas exceções. Mas aqui as modificações podem ou não ser visíveis nessa iteração.
Segunda abordagem ( Stream.forEach
) -
o pedido não está definido. Embora possa não ocorrer para fluxos sequenciais, mas a especificação não garante isso. Também é necessário que a ação seja de natureza não interferente. Como mencionado no doc :
O comportamento desta operação é explicitamente não determinístico. Para pipelines de fluxo paralelo, esta operação não garante o respeito à ordem de encontro do fluxo, pois isso sacrificaria o benefício do paralelismo.
Terceira abordagem ( Stream.forEachOrdered
) -
a ação seria executada na ordem de encontro do fluxo. Portanto, sempre que a ordem importa, use forEachOrdered
sem pensar duas vezes. Conforme mencionado no documento :
Executa uma ação para cada elemento desse fluxo, na ordem de encontro do fluxo, se o fluxo tiver uma ordem de encontro definida.
Ao iterar sobre uma coleção sincronizada, a primeira abordagem retira o bloqueio da coleção uma vez e o mantém em todas as chamadas para o método de ação, mas no caso de fluxos, eles usam o spliterator da coleção, que não bloqueia e depende das regras já estabelecidas de -interferência. Caso a coleta de backup do fluxo seja modificada durante a iteração ConcurrentModificationException
, será lançado um resultado inconsistente.
Quarta Abordagem (Paralela Stream.forEach
) -
Como já mencionado, não há garantia de respeitar a ordem do encontro como esperado no caso de fluxos paralelos. É possível que a ação seja executada em um encadeamento diferente para diferentes elementos que nunca podem ser o caso forEachOrdered
.
Quinto Approach (paralela Stream.forEachOrdered
) -
A forEachOrdered
vai processar os elementos na ordem especificada pela fonte, independentemente do facto de se transmitir é sequencial ou paralelo. Portanto, não faz sentido usar isso com fluxos paralelos.
List
ser? Mostre-nos como você o declarou e instanciaram.