A diferença entre Collection.stream (). ForEach () e Collection.forEach ()
1. Introdução
Existem várias opções para iterar sobre uma coleção em Java. In this short tutorial, we’ll look at two similar looking approaches — Collection.stream().forEach() and Collection.forEach().
Na maioria dos casos, ambos produzirão os mesmos resultados, no entanto, existem algumas diferenças sutis que veremos.
2. Visão geral
Primeiro, vamos criar uma lista para iterar:
List list = Arrays.asList("A", "B", "C", "D");
A maneira mais direta é usar o loop for aprimorado:
for(String s : list) {
//do something with s
}
Se quisermos usar Java de estilo funcional, também podemos usarforEach(). Podemos fazer isso diretamente na coleção:
Consumer consumer = s -> { System.out::println };
list.forEach(consumer);
Ou podemos chamarforEach() no stream da coleção:
list.stream().forEach(consumer);
Ambas as versões irão percorrer a lista e imprimir todos os elementos:
ABCD ABCD
Neste caso simples, não faz diferença quaisforEach() usamos.
3. Ordem de Execução
Collection.forEach() usa o iterador da coleção (se houver um especificado). Isso significa que a ordem de processamento dos itens está definida. Em contraste, a ordem de processamento deCollection.stream().forEach() é indefinida.
Na maioria dos casos, não faz diferença qual dos dois escolhemos.
3.1. Fluxos Paralelos
Fluxos paralelos nos permitem executar o fluxo em vários encadeamentos e, nessas situações, a ordem de execução é indefinida. Java requer apenas que todos os threads terminem antes que qualquer operação de terminal, comoCollectors.toList(), seja chamada.
Vejamos um exemplo em que primeiro chamamosforEach() diretamente na coleção e, segundo, em um fluxo paralelo:
list.forEach(System.out::print);
System.out.print(" ");
list.parallelStream().forEach(System.out::print);
Se executarmos o código várias vezes, veremos quelist.forEach() processa os itens na ordem de inserção, enquantolist.parallelStream().forEach() produz um resultado diferente a cada execução.
Uma saída possível é:
ABCD CDBA
Outro é:
ABCD DBCA
3.2. Iteradores personalizados
Vamos definir uma lista com um iterador personalizado para iterar a coleção na ordem inversa:
class ReverseList extends ArrayList {
@Override
public Iterator iterator() {
int startIndex = this.size() - 1;
List list = this;
Iterator it = new Iterator() {
private int currentIndex = startIndex;
@Override
public boolean hasNext() {
return currentIndex >= 0;
}
@Override
public String next() {
String next = list.get(currentIndex);
currentIndex--;
return next;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
return it;
}
}
Quando iteramos na lista, novamente comforEach() diretamente na coleção e, em seguida, no fluxo:
List myList = new ReverseList();
myList.addAll(list);
myList.forEach(System.out::print);
System.out.print(" ");
myList.stream().forEach(System.out::print);
Temos resultados diferentes:
DCBA ABCD
A razão para os resultados diferentes é queforEach() usado diretamente na lista usa o iterador personalizado,while stream().forEach() simply takes elements one by one from the list, ignoring the iterator.
4. Modificação da coleção
Muitas coleções (por exemplo,ArrayList ouHashSet) não devem ser modificadas estruturalmente durante a iteração sobre elas. Se um elemento for removido ou adicionado durante uma iteração, obteremos uma exceçãoConcurrentModification.
Além disso, as coleções são projetadas para falhar rapidamente, o que significa que a exceção é lançada assim que houver uma modificação.
Da mesma forma, obteremos uma exceçãoConcurrentModification quando adicionarmos ou removermos um elemento durante a execução do pipeline de fluxo. No entanto, a exceção será lançada posteriormente.
Outra diferença sutil entre os dois métodosforEach() é que Java permite explicitamente a modificação de elementos usando o iterador. Os fluxos, ao contrário, não devem interferir.
Vejamos como remover e modificar elementos com mais detalhes.
4.1. Removendo um elemento
Vamos definir uma operação que remove o último elemento (“D”) de nossa lista:
Consumer removeElement = s -> {
System.out.println(s + " " + list.size());
if (s != null && s.equals("A")) {
list.remove("D");
}
};
Quando iteramos sobre a lista, o último elemento é removido após a impressão do primeiro elemento ("A"):
list.forEach(removeElement);
ComoforEach() é rápido para falhas, paramos de iterar e vemos uma exceção antes que o próximo elemento seja processado:
A 4
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList.forEach(ArrayList.java:1252)
at ReverseList.main(ReverseList.java:1)
Vamos ver o que acontece se usarmosstream().forEach() em vez disso:
list.stream().forEach(removeElement);
Aqui, continuamos iterando em toda a lista antes de ver uma exceção:
A 4
B 3
C 3
null 3
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1380)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at ReverseList.main(ReverseList.java:1)
No entanto, o Java não garante que umConcurrentModificationException seja lançado. That means we should never write a program that depends on this exception.
4.2. Alterar elementos
Podemos alterar um elemento enquanto iteramos sobre uma lista:
list.forEach(e -> {
list.set(3, "E");
});
No entanto, embora não haja nenhum problema em fazer isso usandoCollection.forEach() oustream().forEach(), Java requer que uma operação em um fluxo não interfira. Isso significa que os elementos não devem ser modificados durante a execução do pipeline de fluxo.
A razão por trás disso é que o fluxo deve facilitar a execução paralela. Here, modifying elements of a stream could lead to unexpected behavior.
5. Conclusão
Neste artigo, vimos alguns exemplos que mostram as diferenças sutis entreCollection.forEach()eCollection.stream().forEach().
No entanto, é importante observar que todos os exemplos mostrados acima são triviais e destinam-se apenas a comparar as duas maneiras de iterar em uma coleção. Não devemos escrever código cuja exatidão dependa do comportamento mostrado.
Se não exigirmos um fluxo, mas quisermos apenas iterar em uma coleção, a primeira escolha deve ser usarforEach() diretamente na coleção.
O código-fonte para os exemplos neste artigois available over on GitHub.