Para casos simples, como o ilustrado, eles são basicamente os mesmos. No entanto, existem várias diferenças sutis que podem ser significativas.
Um problema é com o pedido. Com Stream.forEach
, o pedido é indefinido . É improvável que ocorra com fluxos sequenciais, ainda assim, está dentro da especificação para Stream.forEach
execução em alguma ordem arbitrária. Isso ocorre frequentemente em fluxos paralelos. Por outro lado, Iterable.forEach
é sempre executado na ordem de iteração de Iterable
, se um for especificado.
Outra questão é com efeitos colaterais. A ação especificada em Stream.forEach
é necessária para não interferir . (Consulte o documento do pacote java.util.stream .) Iterable.forEach
Possui potencialmente menos restrições. Para as coleções em java.util
, Iterable.forEach
geralmente usará as coleções Iterator
, a maioria projetada para ser à prova de falhas e que será lançada ConcurrentModificationException
se a coleção for estruturalmente modificada durante a iteração. No entanto, modificações que não são estruturais são permitidas durante a iteração. Por exemplo, a documentação da classe ArrayList diz que "apenas definir o valor de um elemento não é uma modificação estrutural". Assim, a ação paraArrayList.forEach
é permitido definir valores no subjacente ArrayList
sem problemas.
As coleções simultâneas são novamente diferentes. Em vez de serem rápidos, eles são projetados para serem fracamente consistentes . A definição completa está nesse link. Por um breve momento, considere ConcurrentLinkedDeque
. A ação passou a seu forEach
método é permitido modificar o deque subjacente, mesmo estruturalmente, e ConcurrentModificationException
nunca é lançada. No entanto, a modificação que ocorre pode ou não ser visível nesta iteração. (Daí a consistência "fraca".)
Ainda outra diferença é visível se Iterable.forEach
estiver repetindo uma coleção sincronizada. Nessa coleção, Iterable.forEach
pega o bloqueio da coleção uma vez e o mantém em todas as chamadas para o método de ação. A Stream.forEach
chamada usa o separador da coleção, que não é bloqueado e depende da regra predominante de não interferência. A coleção de backup do fluxo pode ser modificada durante a iteração e, se for, ConcurrentModificationException
pode resultar em um comportamento inconsistente.
Iterable.forEach takes the collection's lock
. De onde é essa informação? Não consigo encontrar esse comportamento nas fontes JDK.ArrayList
a verificação bastante rigorosa de modificações simultâneas e, portanto, geralmente são lançadasConcurrentModificationException
. Mas isso não é garantido, principalmente para fluxos paralelos. Em vez do CME, você pode obter uma resposta inesperada. Considere também modificações não estruturais na origem do fluxo. Para fluxos paralelos, você não sabe qual thread processará um elemento específico, nem se foi processado no momento em que foi modificado. Isso define uma condição de corrida, na qual você pode obter resultados diferentes a cada corrida e nunca obter um CME.Essa resposta se preocupa com o desempenho das várias implementações dos loops. É apenas marginalmente relevante para loops que são chamados MUITAS VEZES (como milhões de chamadas). Na maioria dos casos, o conteúdo do loop será de longe o elemento mais caro. Para situações em que você faz um loop com muita frequência, isso ainda pode ser interessante.
Você deve repetir esses testes no sistema de destino, pois isso é específico da implementação ( código fonte completo ).
Executo o openjdk versão 1.8.0_111 em uma máquina Linux rápida.
Eu escrevi um teste que faz um loop 10 ^ 6 vezes em uma lista usando esse código com tamanhos variados para
integers
(10 ^ 0 -> 10 ^ 5 entradas).Os resultados estão abaixo, o método mais rápido varia dependendo da quantidade de entradas na lista.
Mas, mesmo nas piores situações, o loop de 10 ^ 5 entradas 10 ^ 6 vezes levou 100 segundos para o pior desempenho; portanto, outras considerações são mais importantes em praticamente todas as situações.
Aqui estão meus horários: milissegundos / função / número de entradas na lista. Cada execução é de 10 ^ 6 loops.
Se você repetir o experimento, publiquei o código fonte completo . Edite esta resposta e adicione resultados com uma notação do sistema testado.
Usando um MacBook Pro, Intel Core i7 de 2,5 GHz, 16 GB, macOS 10.12.6:
VM de ponto de acesso Java 8 - Intel Xeon de 3,4 GHz, 8 GB, Windows 10 Pro
VM de ponto de acesso Java 11 - Intel Xeon de 3,4 GHz, 8 GB, Windows 10 Pro
(mesma máquina que acima, versão diferente do JDK)
Java 11 OpenJ9 VM - Intel Xeon de 3,4 GHz , 8 GB, Windows 10 Pro
(mesma máquina e versão JDK que a anterior, VM diferente)
VM de ponto de acesso Java 8 - AMD de 2,8 GHz, 64 GB, Windows Server 2016
VM de ponto de acesso Java 11 - AMD de 2,8 GHz, 64 GB, Windows Server 2016
(mesma máquina que acima, versão JDK diferente)
Java 11 OpenJ9 VM - 2.8GHz AMD, 64 GB, Windows Server 2016
(mesma máquina e versão JDK que a anterior, VM diferente)
A implementação da VM que você escolhe também faz a diferença Hotspot / OpenJ9 / etc.
fonte
Não há diferença entre os dois que você mencionou, pelo menos conceitualmente,
Collection.forEach()
é apenas uma abreviação.Internamente, a
stream()
versão possui um pouco mais de sobrecarga devido à criação do objeto, mas, observando o tempo de execução, ela também não possui uma sobrecarga.Ambas as implementações acabam repetindo o
collection
conteúdo uma vez e, durante a iteração, imprimem o elemento.fonte
Stream
criado ou aos objetos individuais? AFAIK, aStream
não duplica os elementos.Collection.forEach () usa o iterador da coleção (se um for especificado). Isso significa que a ordem de processamento dos itens está definida. Por outro lado, a ordem de processamento de Collection.stream (). ForEach () é indefinida.
Na maioria dos casos, não faz diferença qual dos dois escolhemos. 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 encadeamentos sejam concluídos antes que qualquer operação de terminal, como Collectors.toList (), seja chamada. Vejamos um exemplo em que chamamos primeiro forEach () diretamente na coleção e, em segundo lugar, em um fluxo paralelo:
Se executarmos o código várias vezes, veremos que list.forEach () processa os itens em ordem de inserção, enquanto list.parallelStream (). ForEach () produz um resultado diferente a cada execução. Uma saída possível é:
Outro é:
fonte