Java 8 Iterable.forEach () vs loop foreach


466

Qual das seguintes opções é a melhor prática no Java 8?

Java 8:

joins.forEach(join -> mIrc.join(mSession, join));

Java 7:

for (String join : joins) {
    mIrc.join(mSession, join);
}

Eu tenho muitos loops que poderiam ser "simplificados" com lambdas, mas há realmente alguma vantagem em usá-los? Melhoraria seu desempenho e legibilidade?

EDITAR

Também estenderei essa pergunta a métodos mais longos. Eu sei que você não pode retornar ou interromper a função pai de um lambda e isso também deve ser levado em consideração ao compará-los, mas há mais alguma coisa a ser considerada?


10
Não há nenhuma vantagem real de desempenho uma sobre a outra. A primeira opção é algo inspirado no FP (o que é comumente mencionado como uma maneira mais "agradável" e "clara" de expressar seu código). Na realidade - esta é uma questão bastante "estilo".
Eugene Loy

5
@ Dwb: neste caso, isso não é relevante. forEach não é definido como paralelo ou algo parecido; portanto, essas duas coisas são semanticamente equivalentes. Obviamente, é possível implementar uma versão paralela do forEach (e uma já pode estar presente na biblioteca padrão) e, nesse caso, a sintaxe da expressão lambda seria muito útil.
AardvarkSoup

8
@AardvarkSoup A instância na qual forEach é chamado é um Stream ( lambdadoc.net/api/java/util/stream/Stream.html ). Para solicitar uma execução paralela poderia escrever joins.parallel () foreach (...).
mschenk74

13
É joins.forEach((join) -> mIrc.join(mSession, join));realmente uma "simplificação" de for (String join : joins) { mIrc.join(mSession, join); }? Você aumentou a contagem de pontuação de 9 para 12, para o benefício de ocultar o tipo de join. O que você realmente fez é colocar duas declarações em uma linha.
Tom Hawtin - tackline

7
Outro ponto a considerar é a capacidade limitada de captura de variáveis ​​do Java. Com Stream.forEach (), você não pode atualizar variáveis ​​locais, pois sua captura as torna finais, o que significa que você pode ter um comportamento com estado no lambe forEach (a menos que esteja preparado para alguma feiúra, como usar variáveis ​​de estado de classe).
RedGlyph

Respostas:


511

A melhor prática é usar for-each. Além de violar o princípio Keep It Simple, Stupid , o novo fangled forEach()tem pelo menos as seguintes deficiências:

  • Não é possível usar variáveis ​​não finais . Portanto, código como o seguinte não pode ser transformado em um lambda forEach:

    Object prev = null;
    for(Object curr : list)
    {
        if( prev != null )
            foo(prev, curr);
        prev = curr;
    }
  • Não é possível lidar com exceções verificadas . Os lambdas não são realmente proibidos de lançar exceções verificadas, mas interfaces funcionais comuns como Consumernão declaram nenhuma. Portanto, qualquer código que lança exceções verificadas deve colocá-las em try-catchou Throwables.propagate(). Mas mesmo se você fizer isso, nem sempre é claro o que acontece com a exceção lançada. Poderia ser engolido em algum lugar nas entranhas deforEach()

  • Controle de fluxo limitado . A returnem um lambda é igual a continueem um para cada, mas não há equivalente a a break. Também é difícil fazer coisas como valores de retorno, curto-circuito ou sinalizadores de configuração (o que teria aliviado um pouco as coisas, se não fosse uma violação da regra de não variáveis ​​não finais ). "Isso não é apenas uma otimização, mas é crítico quando você considera que algumas seqüências (como a leitura das linhas de um arquivo) podem ter efeitos colaterais ou uma sequência infinita".

  • Pode ser executado em paralelo , o que é uma coisa horrível para todos, menos os 0,1% do seu código que precisam ser otimizados. Qualquer código paralelo precisa ser pensado (mesmo que não use bloqueios, voláteis e outros aspectos particularmente desagradáveis ​​da execução multiencadeada tradicional). Qualquer bug será difícil de encontrar.

  • Pode prejudicar o desempenho , porque o JIT não pode otimizar forEach () + lambda na mesma extensão que os loops simples, especialmente agora que as lambdas são novas. Por "otimização", não quero dizer a sobrecarga de chamar lambdas (que é pequena), mas a análise e transformação sofisticadas que o compilador JIT moderno executa na execução de código.

  • Se você precisar de paralelismo, provavelmente é muito mais rápido e não muito mais difícil usar um ExecutorService . Os fluxos são ambos auto-mágicos (leia-se: não sabem muito sobre o seu problema) e usam uma estratégia de paralelização especializada (leia-se: ineficiente para o caso geral) ( decomposição recursiva de junção de forquilha ).

  • Torna a depuração mais confusa , devido à hierarquia de chamada aninhada e, Deus permita, execução paralela. O depurador pode ter problemas para exibir variáveis ​​do código circundante, e coisas como passo a passo podem não funcionar conforme o esperado.

  • Os fluxos em geral são mais difíceis de codificar, ler e depurar . Na verdade, isso é verdade para APIs " fluentes " complexas em geral. A combinação de instruções únicas complexas, o uso pesado de genéricos e a falta de variáveis ​​intermediárias conspiram para produzir mensagens de erro confusas e frustrar a depuração. Em vez de "esse método não tem uma sobrecarga para o tipo X", você recebe uma mensagem de erro mais próxima de "em algum lugar que você estragou os tipos, mas não sabemos onde ou como". Da mesma forma, você não pode examinar e examinar as coisas em um depurador tão facilmente quanto quando o código é dividido em várias instruções, e os valores intermediários são salvos nas variáveis. Por fim, a leitura do código e a compreensão dos tipos e comportamento em cada estágio da execução pode não ser trivial.

  • Destaca-se como um polegar dolorido . A linguagem Java já possui a instrução for-each. Por que substituí-lo por uma chamada de função? Por que incentivar a ocultação de efeitos colaterais em algum lugar das expressões? Por que incentivar linhas de mão difíceis? Misturar regularmente para cada um e o novo para cada um é um estilo ruim. O código deve falar em expressões idiomáticas (padrões que são rápidos de compreender devido à sua repetição), e quanto menos expressões idiomáticas forem usadas, mais claro será o código e menos tempo será gasto decidindo qual linguagem usar (uma grande perda de tempo para perfeccionistas como eu! )

Como você pode ver, eu não sou muito fã do forEach (), exceto nos casos em que faz sentido.

Particularmente ofensivo para mim é o fato de que Streamele não é implementado Iterable(apesar de ter um método iterator) e não pode ser usado em um for-each, apenas com um forEach (). Eu recomendo converter Streams em Iterables com (Iterable<T>)stream::iterator. Uma alternativa melhor é usar o StreamEx, que corrige vários problemas da API do Stream, incluindo a implementação Iterable.

Dito isto, forEach()é útil para o seguinte:

  • Iterando atomicamente sobre uma lista sincronizada . Antes disso, uma lista gerada com Collections.synchronizedList()era atômica em relação a coisas como get ou set, mas não era segura para threads durante a iteração.

  • Execução paralela (usando um fluxo paralelo apropriado) . Isso poupa algumas linhas de código versus o uso de um ExecutorService, se o seu problema corresponder às premissas de desempenho incorporadas ao Streams e Spliterators.

  • Contêineres específicos que , como a lista sincronizada, se beneficiam de estar no controle da iteração (embora isso seja amplamente teórico, a menos que as pessoas possam trazer mais exemplos)

  • Chamar uma única função de maneira mais limpa usando forEach()um argumento de referência de método (ou seja, list.forEach (obj::someMethod)). No entanto, lembre-se dos pontos sobre exceções verificadas, depuração mais difícil e redução do número de expressões usadas ao escrever código.

Artigos que usei para referência:

EDIT: Parece que algumas das propostas originais para lambdas (como http://www.javac.info/closures-v06a.html ) resolveram alguns dos problemas que mencionei (ao mesmo tempo em que adicionaram suas próprias complicações, é claro).


95
"Por que incentivar a ocultação de efeitos colaterais em algum lugar das expressões?" é a pergunta errada. O funcional forEachexiste para incentivar o estilo funcional, ou seja, usar expressões sem efeitos colaterais. Se você encontrar a situação, forEachele não funciona bem com seus efeitos colaterais, deve ter a sensação de que não está usando a ferramenta certa para o trabalho. Então a resposta simples é: isso é porque você está certo, então fique no loop for-each para isso. O clássico forcircuito não se tornou obsoleto ...
Holger

17
@ Holger Como pode forEachser usado sem efeitos colaterais?
Aleksandr Dubinsky

14
Tudo bem, eu não era preciso o suficiente, forEaché a única operação de fluxo destinada a efeitos colaterais, mas não a efeitos colaterais como o código de exemplo, a contagem é uma reduceoperação típica . Eu sugeriria, como regra geral, manter todas as operações que manipulam variáveis ​​locais ou devem influenciar o fluxo de controle (incluindo manipulação de exceção) em um forloop clássico . Em relação à pergunta original, penso, o problema decorre do fato de alguém usar um fluxo em que um forloop simples sobre a fonte do fluxo seria suficiente. Use um riacho onde forEach()funciona apenas
Holger

8
@ Holger Qual é um exemplo de efeitos colaterais que forEachseria apropriado?
Aleksandr Dubinsky

29
Algo que processa cada item individualmente e não tenta alterar variáveis ​​locais. Por exemplo, manipular os itens em si ou imprimi-los, gravando / enviando-os para um arquivo, fluxo de rede, etc. Não é um problema para mim se você questionar esses exemplos e não encontrar nenhum aplicativo para ele; filtragem, mapeamento, redução, pesquisa e (em menor grau) coleta são as operações preferidas de um fluxo. O forEach me parece uma conveniência para vincular com APIs existentes. E para operações paralelas, é claro. Isso não funciona com forloops.
quer

169

A vantagem é levada em consideração quando as operações podem ser executadas em paralelo. (Consulte http://java.dzone.com/articles/devoxx-2012-java-8-lambda-and - a seção sobre iteração interna e externa)

  • A principal vantagem do meu ponto de vista é que a implementação do que deve ser feito dentro do loop pode ser definida sem a necessidade de decidir se será executado em paralelo ou sequencialmente.

  • Se você quiser que seu loop seja executado em paralelo, você pode simplesmente escrever

     joins.parallelStream().forEach(join -> mIrc.join(mSession, join));

    Você precisará escrever um código extra para o manuseio de threads, etc.

Nota: Para minha resposta, assumi junções implementando a java.util.Streaminterface. Se joins implementa apenas a java.util.Iterableinterface, isso não é mais verdade.


4
Os slides de um engenheiro oracle ao qual ele está se referindo ( blogs.oracle.com/darcy/resource/Devoxx/… ) não mencionam o paralelismo nessas expressões lambda. O paralelismo pode ocorrer nos métodos de coleta em massa, como map& foldque não estão realmente relacionados a lambdas.
Thomas Jungblut

1
Realmente não parece que o código do OP se beneficie do paralelismo automático aqui (especialmente porque não há garantia de que haverá um). Na verdade, não sabemos o que é "mIrc", mas "join" não parece realmente algo que possa ser executado fora de ordem.
Eugene Loy

11
Stream#forEache Iterable#forEachnão são a mesma coisa. O OP está perguntando Iterable#forEach.
gvlasov

2
Usei o estilo UPDATEX, pois houve alterações na especificação entre o momento em que a pergunta foi feita e o tempo em que a resposta foi atualizada. Sem o histórico da resposta, seria ainda mais confuso, pensei.
precisa saber é o seguinte

1
Alguém poderia me explicar por que essa resposta não é válida se joinsestiver implementando em Iterablevez de Stream? A partir de um par de coisas que eu li, OP deve ser capaz de fazer joins.stream().forEach((join) -> mIrc.join(mSession, join));e joins.parallelStream().forEach((join) -> mIrc.join(mSession, join));se joinsimplementaIterable
Blueriver

112

Ao ler esta pergunta, pode-se ter a impressão de que, Iterable#forEachem combinação com expressões lambda, é um atalho / substituição para escrever um loop tradicional para cada um. Isto simplesmente não é verdade. Este código do OP:

joins.forEach(join -> mIrc.join(mSession, join));

é não pretende ser um atalho para a escrita

for (String join : joins) {
    mIrc.join(mSession, join);
}

e certamente não deve ser usado dessa maneira. Em vez disso, destina-se como um atalho (embora seja não exatamente o mesmo) para a escrita

joins.forEach(new Consumer<T>() {
    @Override
    public void accept(T join) {
        mIrc.join(mSession, join);
    }
});

E é um substituto para o seguinte código Java 7:

final Consumer<T> c = new Consumer<T>() {
    @Override
    public void accept(T join) {
        mIrc.join(mSession, join);
    }
};
for (T t : joins) {
    c.accept(t);
}

Substituir o corpo de um loop por uma interface funcional, como nos exemplos acima, torna seu código mais explícito: Você está dizendo que (1) o corpo do loop não afeta o código circundante e o fluxo de controle e (2) o O corpo do loop pode ser substituído por uma implementação diferente da função, sem afetar o código circundante. Não poder acessar variáveis ​​não finais do escopo externo não é um déficit de funções / lambdas, é um recurso que distingue a semântica da Iterable#forEachsemântica de um loop tradicional para cada loop. Uma vez acostumado com a sintaxe de Iterable#forEach, torna o código mais legível, porque você obtém imediatamente essas informações adicionais sobre o código.

Os loops tradicionais para cada certamente continuarão sendo boas práticas (para evitar o termo usado em excesso " melhores práticas ") em Java. Mas isso não significa que isso Iterable#forEachdeva ser considerado uma prática ruim ou um estilo ruim. É sempre uma boa prática usar a ferramenta certa para fazer o trabalho, e isso inclui misturar os loops tradicionais para cada um Iterable#forEach, onde faz sentido.

Como as desvantagens de Iterable#forEachjá foram discutidas neste segmento, aqui estão alguns motivos pelos quais você provavelmente deseja usar Iterable#forEach:

  • Para tornar seu código mais explícito: Como descrito acima, você Iterable#forEach pode torná-lo mais explícito e legível em algumas situações.

  • Para tornar seu código mais extensível e sustentável: O uso de uma função como corpo de um loop permite substituir essa função por diferentes implementações (consulte Padrão de estratégia ). Você pode, por exemplo, substituir facilmente a expressão lambda por uma chamada de método, que pode ser substituída por subclasses:

    joins.forEach(getJoinStrategy());

    Em seguida, você pode fornecer estratégias padrão usando uma enumeração, que implementa a interface funcional. Isso não apenas torna seu código mais extensível, como também aumenta a capacidade de manutenção, porque desacopla a implementação do loop da declaração do loop.

  • Para tornar seu código mais depurável: Separar a implementação do loop da declaração também pode facilitar a depuração, porque você pode ter uma implementação de depuração especializada, que imprime mensagens de depuração, sem a necessidade de desorganizar o código principal if(DEBUG)System.out.println(). A implementação de depuração poderia, por exemplo, ser um delegado , que decora a implementação real da função.

  • Para otimizar o código crítico de desempenho: Ao contrário de algumas das asserções deste encadeamento, Iterable#forEach ele já oferece melhor desempenho do que um loop tradicional para cada loop, pelo menos ao usar ArrayList e executar Hotspot no modo "-client". Embora esse aumento de desempenho seja pequeno e insignificante para a maioria dos casos de uso, há situações em que esse desempenho extra pode fazer a diferença. Por exemplo, os mantenedores de bibliotecas certamente quererão avaliar se algumas de suas implementações de loop existentes devem ser substituídas por Iterable#forEach.

    Para apoiar esta afirmação com fatos, fiz alguns micro-benchmarks com o Caliper . Aqui está o código de teste (é necessário o último Caliper do git):

    @VmOptions("-server")
    public class Java8IterationBenchmarks {
    
        public static class TestObject {
            public int result;
        }
    
        public @Param({"100", "10000"}) int elementCount;
    
        ArrayList<TestObject> list;
        TestObject[] array;
    
        @BeforeExperiment
        public void setup(){
            list = new ArrayList<>(elementCount);
            for (int i = 0; i < elementCount; i++) {
                list.add(new TestObject());
            }
            array = list.toArray(new TestObject[list.size()]);
        }
    
        @Benchmark
        public void timeTraditionalForEach(int reps){
            for (int i = 0; i < reps; i++) {
                for (TestObject t : list) {
                    t.result++;
                }
            }
            return;
        }
    
        @Benchmark
        public void timeForEachAnonymousClass(int reps){
            for (int i = 0; i < reps; i++) {
                list.forEach(new Consumer<TestObject>() {
                    @Override
                    public void accept(TestObject t) {
                        t.result++;
                    }
                });
            }
            return;
        }
    
        @Benchmark
        public void timeForEachLambda(int reps){
            for (int i = 0; i < reps; i++) {
                list.forEach(t -> t.result++);
            }
            return;
        }
    
        @Benchmark
        public void timeForEachOverArray(int reps){
            for (int i = 0; i < reps; i++) {
                for (TestObject t : array) {
                    t.result++;
                }
            }
        }
    }

    E aqui estão os resultados:

    Ao executar com "-client", Iterable#forEachsupera o loop for tradicional sobre um ArrayList, mas ainda é mais lento que a iteração direta sobre um array. Ao executar com "-server", o desempenho de todas as abordagens é praticamente o mesmo.

  • Para fornecer suporte opcional à execução paralela: Já foi dito aqui que a possibilidade de executar a interface funcional Iterable#forEachem paralelo usando fluxos é certamente um aspecto importante. Como Collection#parallelStream()não garante que o loop seja realmente executado em paralelo, deve-se considerar isso um recurso opcional . Ao iterar sua lista com list.parallelStream().forEach(...);, você diz explicitamente: Esse loop suporta execução paralela, mas não depende disso. Novamente, esse é um recurso e não um déficit!

    Ao afastar a decisão de execução paralela da implementação real do loop, você permite a otimização opcional do seu código, sem afetar o próprio código, o que é uma coisa boa. Além disso, se a implementação padrão do fluxo paralelo não atender às suas necessidades, ninguém o impedirá de fornecer sua própria implementação. Você pode, por exemplo, fornecer uma coleção otimizada, dependendo do sistema operacional subjacente, do tamanho da coleção, do número de núcleos e de algumas configurações de preferência:

    public abstract class MyOptimizedCollection<E> implements Collection<E>{
        private enum OperatingSystem{
            LINUX, WINDOWS, ANDROID
        }
        private OperatingSystem operatingSystem = OperatingSystem.WINDOWS;
        private int numberOfCores = Runtime.getRuntime().availableProcessors();
        private Collection<E> delegate;
    
        @Override
        public Stream<E> parallelStream() {
            if (!System.getProperty("parallelSupport").equals("true")) {
                return this.delegate.stream();
            }
            switch (operatingSystem) {
                case WINDOWS:
                    if (numberOfCores > 3 && delegate.size() > 10000) {
                        return this.delegate.parallelStream();
                    }else{
                        return this.delegate.stream();
                    }
                case LINUX:
                    return SomeVerySpecialStreamImplementation.stream(this.delegate.spliterator());
                case ANDROID:
                default:
                    return this.delegate.stream();
            }
        }
    }

    O bom aqui é que sua implementação de loop não precisa saber ou se preocupar com esses detalhes.


5
Você tem uma visão interessante nesta discussão e traz vários pontos. Vou tentar abordá-los. Você propõe alternar entre forEache com for-eachbase em alguns critérios relacionados à natureza do corpo do loop. Sabedoria e disciplina para seguir essas regras são a marca registrada de um bom programador. Essas regras também são uma desgraça, porque as pessoas ao seu redor não as seguem ou discordam. Por exemplo, usando exceções marcadas vs não marcadas. Essa situação parece ainda mais sutil. Mas, se o corpo "não afeta o código surround ou o controle de fluxo", não é melhor considerar isso como uma função?
Aleksandr Dubinsky

4
Obrigado pelos comentários detalhados Aleksandr. But, if the body "does not affect surround code or flow control," isn't factoring it out as a function better?. Sim, isso geralmente acontece na minha opinião - fatorar esses loops como funções é uma consequência natural.
Balder

2
Em relação ao problema de desempenho - acho que depende muito da natureza do loop. Em um projeto em que estou trabalhando, tenho usado loops de estilo de função semelhantes aos Iterable#forEachanteriores ao Java 8 apenas por causa do aumento de desempenho. O projeto em questão possui um loop principal semelhante a um loop de jogo, com um número indefinido de sub-loops aninhados, onde os clientes podem conectar participantes do loop como funções. Essa estrutura de software se beneficia bastante Iteable#forEach.
Balder

7
Há uma frase bem no final da minha crítica: "O código deve falar em expressões idiomáticas, e quanto menos expressões são usadas, mais claro é o código e menos tempo é gasto para decidir qual idioma usar". Comecei a apreciar profundamente esse ponto quando mudei de C # para Java.
Aleksandr Dubinsky

7
Esse é um argumento ruim. Você pode usá-lo para justificar o que quiser: por que você não deve usar um loop for, porque um loop while é bom o suficiente e esse é um idioma a menos. Heck, por que usar qualquer loop, switch ou try / catch quando goto pode fazer tudo isso e muito mais.
Tapichu

13

forEach()pode ser implementado para ser mais rápido do que para cada loop, porque o iterável sabe a melhor maneira de iterar seus elementos, em oposição à maneira do iterador padrão. Portanto, a diferença é loop interno ou externo.

Por exemplo, ArrayList.forEach(action)pode ser simplesmente implementado como

for(int i=0; i<size; i++)
    action.accept(elements[i])

em oposição ao loop for-each, que requer muitos andaimes

Iterator iter = list.iterator();
while(iter.hasNext())
    Object next = iter.next();
    do something with `next`

No entanto, também precisamos contabilizar dois custos indiretos usando forEach(): um está criando o objeto lambda e o outro está invocando o método lambda. Eles provavelmente não são significativos.

consulte também http://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/ para comparar iterações internas / externas para diferentes casos de uso.


8
por que o iterável sabe o melhor caminho, mas o iterador não?
mschenk74

2
não há diferença essencial, mas é necessário um código extra para estar em conformidade com a interface do iterador, o que pode ser mais caro.
ZhongYu

1
@ zhong.j.yu se você implementar o Collection, você também implementa o Iterable de qualquer maneira. Portanto, não há sobrecarga de código em termos de "adicionar mais código para implementar os métodos de interface ausentes", se esse é o seu ponto. Como o mschenk74 disse, parece que não há motivos para que você não possa ajustar seu iterador para saber como iterar sua coleção da melhor maneira possível. Concordo que pode haver sobrecarga para iterador criação, mas a sério, as coisas geralmente tão baratos, que você pode dizer que eles têm custo zero ...
Eugene Loy

4
por exemplo iteração uma árvore: void forEach(Consumer<T> v){leftTree.forEach(v);v.accept(rootElem);rightTree.forEach(v);}, este é mais elegante do que a iteração externa, e você pode decidir sobre a melhor forma de sincronizar
catraca aberração

1
Curiosamente, o único comentário nos String.joinmétodos (ok, junção incorreta) é "Número de elementos que provavelmente não valem Arrays.stream overhead". então eles usam um laço for fino.
Tom Hawtin - tackline

7

TL; DR : List.stream().forEach()foi o mais rápido.

Eu senti que deveria adicionar meus resultados da iteração de benchmarking. Adotei uma abordagem muito simples (sem estruturas de benchmarking) e avaliei 5 métodos diferentes:

  1. clássico for
  2. foreach clássico
  3. List.forEach()
  4. List.stream().forEach()
  5. List.parallelStream().forEach

o procedimento de teste e parâmetros

private List<Integer> list;
private final int size = 1_000_000;

public MyClass(){
    list = new ArrayList<>();
    Random rand = new Random();
    for (int i = 0; i < size; ++i) {
        list.add(rand.nextInt(size * 50));
    }    
}
private void doIt(Integer i) {
    i *= 2; //so it won't get JITed out
}

A lista nesta classe deve ser repetida e doIt(Integer i)aplicada a todos os seus membros, a cada vez através de um método diferente. na classe Main, eu executo o método testado três vezes para aquecer a JVM. Em seguida, executo o método de teste 1000 vezes, somando o tempo necessário para cada método de iteração (usando System.nanoTime()). Depois disso, divido essa soma por 1000 e esse é o resultado, o tempo médio. exemplo:

myClass.fored();
myClass.fored();
myClass.fored();
for (int i = 0; i < reps; ++i) {
    begin = System.nanoTime();
    myClass.fored();
    end = System.nanoTime();
    nanoSum += end - begin;
}
System.out.println(nanoSum / reps);

Eu executei isso em uma CPU i5 de 4 núcleos, com a versão java 1.8.0_05

clássico for

for(int i = 0, l = list.size(); i < l; ++i) {
    doIt(list.get(i));
}

tempo de execução: 4,21 ms

foreach clássico

for(Integer i : list) {
    doIt(i);
}

tempo de execução: 5,95 ms

List.forEach()

list.forEach((i) -> doIt(i));

tempo de execução: 3,11 ms

List.stream().forEach()

list.stream().forEach((i) -> doIt(i));

tempo de execução: 2,79 ms

List.parallelStream().forEach

list.parallelStream().forEach((i) -> doIt(i));

tempo de execução: 3,6 ms


23
Como você consegue esses números? Qual estrutura de referência você está usando? Se você não estiver usando nenhum e simplesmente System.out.printlnexibir esses dados de forma ingênua, todos os resultados serão inúteis.
Luiggi Mendoza 13/01/2015

2
Sem estrutura. Eu uso System.nanoTime(). Se você ler a resposta, verá como foi feita. Eu não acho que isso torne inútil, visto que esta é uma pergunta relativa . Não me importo com o desempenho de um determinado método, mas com o desempenho de outros métodos.
Assaf

31
E esse é o objetivo de uma boa micro referência. Como você não atendeu a esses requisitos, os resultados são inúteis.
Luiggi Mendoza 15/01/15

6
Posso recomendar conhecer JMH vez, este é o que está sendo usado por si e coloca um grande esforço de obter números corretos Java: openjdk.java.net/projects/code-tools/jmh
dsvensson

1
Eu concordo com @LuiggiMendoza. Não há como saber se esses resultados são consistentes ou válidos. Deus sabe quantos benchmarks eu fiz que continuam relatando resultados diferentes, especialmente dependendo da ordem da iteração, tamanho e quais não.
mmm

6

Sinto que preciso estender um pouco meu comentário ...

Sobre paradigma \ estilo

Esse é provavelmente o aspecto mais notável. O FP se tornou popular devido ao que você pode obter evitando os efeitos colaterais. Não vou me aprofundar em quais prós e contras você pode obter disso, pois isso não está relacionado à questão.

No entanto, direi que a iteração usando Iterable.forEach é inspirada no FP e, em vez disso, resulta em trazer mais FP para Java (ironicamente, eu diria que não há muito uso do forEach no FP puro, pois ele não faz nada, exceto introduzir efeitos colaterais).

No final, eu diria que é uma questão de gosto, estilo e paradigma em que você está escrevendo.

Sobre paralelismo.

Do ponto de vista do desempenho, não há benefícios notáveis ​​prometidos com o uso de Iterable.forEach over foreach (...).

De acordo com os documentos oficiais do Iterable.forEach :

Executa a ação especificada no conteúdo do Iterable, na ordem em que os elementos ocorrem durante a iteração, até que todos os elementos tenham sido processados ​​ou a ação lança uma exceção.

... ou seja, documenta claramente que não haverá paralelismo implícito. Adicionar um seria violação do LSP.

Agora, existem "coleções paralelas" que são prometidas no Java 8, mas para trabalhar com aquelas que você precisa para mim, é mais explícito e se preocupa mais com elas (veja a resposta de mschenk74, por exemplo).

BTW: nesse caso, Stream.forEach será usado e não garante que o trabalho real seja realizado em paralelo (depende da coleção subjacente).

ATUALIZAÇÃO: pode não ser tão óbvio e um pouco esticado à primeira vista, mas há outra faceta de estilo e perspectiva de legibilidade.

Primeiro de tudo - as planícies antigas são planas e antigas. Todo mundo já os conhece.

Segundo, e mais importante - você provavelmente deseja usar o Iterable.forEach apenas com lambdas de uma linha. Se o "corpo" ficar mais pesado - eles tendem a não ser tão legíveis. Você tem duas opções a partir daqui - use classes internas (eca) ou use forloop antigo simples. As pessoas geralmente ficam aborrecidas quando vêem as mesmas coisas (iterando sobre coleções) sendo executadas de várias formas / estilos na mesma base de código, e esse parece ser o caso.

Novamente, isso pode ou não ser um problema. Depende das pessoas que trabalham no código.


1
O paralelismo não precisa de novas "coleções paralelas". Depende apenas se você solicitou um fluxo sequencial (usando collection.stream ()) ou um paralelo (usando collection.parallelStream ()).
JB Nizet

@JBNizet De acordo com os documentos, Collection.parallelStream () não garante que a implementação da coleção retorne o fluxo paralelo. Na verdade, estou me perguntando quando isso pode acontecer, mas provavelmente isso depende da coleta.
Eugene Loy

acordado. Também depende da coleção. Mas meu argumento era que os loops foreach paralelos já estavam disponíveis com todas as coleções padrão (ArrayList, etc.). Não é necessário aguardar "coleções paralelas".
JB Nizet

O @JBNizet concorda com o seu ponto, mas não é exatamente isso que eu quis dizer com "coleções paralelas". Refiro Collection.parallelStream () que foi adicionado no Java 8 como "coleções paralelas" pela analogia ao conceito de Scala que faz praticamente o mesmo. Além disso, não sei como é chamado no bit do JSR. Vi alguns papéis que usam a mesma terminologia para esse recurso do Java 8.
Eugene Loy

1
para o último parágrafo, você pode usar uma referência de função:collection.forEach(MyClass::loopBody);
aberração catraca

6

Uma das limitações mais funcionais forEachda funcionalidade é a falta de suporte a exceções verificadas.

Uma solução possível é substituir o terminal forEachpor um loop foreach antigo simples:

    Stream<String> stream = Stream.of("", "1", "2", "3").filter(s -> !s.isEmpty());
    Iterable<String> iterable = stream::iterator;
    for (String s : iterable) {
        fileWriter.append(s);
    }

Aqui está uma lista das perguntas mais populares com outras soluções alternativas no tratamento de exceções verificadas dentro de lambdas e fluxos:

Função Java 8 Lambda que gera exceção?

Java 8: Lambda-Streams, filtrar por método com exceção

Como posso lançar exceções CHECKED de dentro dos fluxos do Java 8?

Java 8: Tratamento de exceções verificadas obrigatórias em expressões lambda. Por que obrigatório, não opcional?


3

A vantagem do método Java 1.8 forEach sobre o 1.7 Enhanced for loop é que, ao escrever o código, você pode se concentrar apenas na lógica de negócios.

O método forEach usa o objeto java.util.function.Consumer como argumento, portanto, ajuda a ter nossa lógica de negócios em um local separado para que você possa reutilizá-lo a qualquer momento.

Veja o snippet abaixo,

  • Aqui eu criei uma nova classe que substituirá o método de classe de aceitação da classe de consumidor, onde você pode adicionar funcionalidade adicional, mais do que iteração .. !!!!!!

    class MyConsumer implements Consumer<Integer>{
    
        @Override
        public void accept(Integer o) {
            System.out.println("Here you can also add your business logic that will work with Iteration and you can reuse it."+o);
        }
    }
    
    public class ForEachConsumer {
    
        public static void main(String[] args) {
    
            // Creating simple ArrayList.
            ArrayList<Integer> aList = new ArrayList<>();
            for(int i=1;i<=10;i++) aList.add(i);
    
            //Calling forEach with customized Iterator.
            MyConsumer consumer = new MyConsumer();
            aList.forEach(consumer);
    
    
            // Using Lambda Expression for Consumer. (Functional Interface) 
            Consumer<Integer> lambda = (Integer o) ->{
                System.out.println("Using Lambda Expression to iterate and do something else(BI).. "+o);
            };
            aList.forEach(lambda);
    
            // Using Anonymous Inner Class.
            aList.forEach(new Consumer<Integer>(){
                @Override
                public void accept(Integer o) {
                    System.out.println("Calling with Anonymous Inner Class "+o);
                }
            });
        }
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.