Java 8 Iterable.forEach () vs foreach loop

Qual das seguintes é a melhor prática em Java 8?

Java 8:

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

Java 7:

for (String join : joins) {
    mIrc.join(mSession, join);
}
Tenho muitos para loops que poderiam ser "simplificados" com lambdas, mas há realmente alguma vantagem de usá-los? Melhoraria o seu desempenho e legibilidade?

EDITAR

Também vou alargar esta questão a métodos mais longos. Eu sei que você não pode voltar ou quebrar a função de pai de um lambda e isso também deve ser levado para consideração ao compará-los, mas há algo mais a ser considerado?

Author: Rafael, 2013-05-19

8 answers

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

  • A principal vantagem, do meu ponto de vista, é que a implementação do que deve ser feito no âmbito do lacete pode ser definida sem ter de decidir se será executado em paralelo ou sequencial

  • Se quiser que o seu loop seja executado em paralelo você poderia simplesmente escrever

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

    Terá de escrever algum código extra para o tratamento de tópicos, etc.

Nota: para a minha resposta, presumi que me juntaria a implementar a interface java.util.Stream. Se o joins implementa apenas a interface java.util.Iterable isto já não é verdade.

 157
Author: mschenk74, 2018-06-29 07:40:06

A melhor prática é usar for-each. Além de violar o princípio manter simples e estúpido , o recém-criado forEach()tem pelo menos as seguintes deficiências:

  • Não é possível utilizar variáveis não finais. Então, um 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 consigo lidar com as excepções verificadas. Lambdas não são proibidos de abrir excepções controladas, mas são comuns. interfaces funcionais como Consumer não declaram nenhuma. Portanto, qualquer código que lance excepções assinaladas deve envolvê-las em try-catch ou Throwables.propagate(). Mas mesmo que faças isso, nem sempre é claro o que acontece à excepção. Pode ser engolido algures nas entranhas de forEach()

  • Controlo limitado do fluxo. A return em um lambda é igual a continue em A para-cada, mas não há equivalente a break. Também é difícil fazer coisas como valores de retorno, curto-circuito, ou definir bandeiras (o que teria aliviado um pouco as coisas, se não fosse uma violação da regra sem variáveis não finais). " isto não é apenas uma optimização, mas é crítico quando se considera que algumas sequências (como ler as linhas de um ficheiro) podem ter efeitos secundários, ou pode ter uma sequência infinita."

  • Pode ser executado em paralelo, o que é uma coisa horrível, horrível para todos, menos para os 0,1% do seu código que precisa ser otimizado. As o código paralelo tem de ser pensado (mesmo que não use Fechaduras, voláteis e outros aspectos particularmente desagradáveis da execução multi-roscada tradicional). Qualquer insecto será difícil de encontrar.

  • Pode prejudicar o desempenho , porque o JIT não pode otimizar forEach ()+lambda na mesma medida que loops simples, especialmente agora que lambdas são novos. Por "otimização" eu não quero dizer a sobrecarga de chamar lambdas (que é pequeno), mas para a análise sofisticada e transformação que o compilador JIT moderno executa em execução de código.

  • Se você precisa de paralelismo, provavelmente é muito mais rápido e não muito mais difícil usar um ExecutorService . Os fluxos são ambos automágicos (Leia: não sei muito sobre o seu problema) e usam uma estratégia de paralelização especializada (leia: ineficiente para o caso geral) (} fork-join decomposição recursiva).

  • Torna a depuração mais confusa , por causa da hierarquia de chamadas aninhada e, Deus nos Livre, execução paralela. O depurador pode ter problemas mostrando variáveis do Código circundante, e coisas como step-through podem não funcionar como esperado.

  • Os fluxos em geral são mais difíceis de codificar, ler e depurar . Na verdade, isto é verdade para o complexo "fluente" APIs em geral. A combinação de afirmações simples complexas, uso pesado de genéricos, e falta de variáveis intermediárias conspire para produzir mensagens de erro confusas e frustrar a depuração. Em vez de" this method doesn't have an overload for type X", você recebe uma mensagem de erro mais perto de " somewhere you messed up the types, but we don't know where or how."Da mesma forma, você não pode avançar e examinar as coisas em um depurador tão facilmente como quando o código é dividido em múltiplas declarações, e valores intermediários são salvos para variáveis. Finalmente, ler o código e compreender os tipos e o comportamento em cada fase de a execução pode não ser trivial.

  • Destaca-se como um polegar dorido. A linguagem Java já tem a declaração para-cada. Porquê substituí-lo por uma chamada? Porquê incentivar a esconder efeitos secundários algures nas expressões? Porquê encorajar uníssono inflexível? Misturar regular para-cada e novo forEach willy-nilly é um mau estilo. Código deve falar em idiomas( padrões que são rápidos de compreender devido à sua repetição), e quanto menos idiomas são usados, mais claro o código é e menos tempo é gasto decidindo qual idioma usar (uma grande perda de tempo para perfeccionistas como eu!).

Como podem ver, não sou grande fã dos forEach, excepto nos casos em que faz sentido. Particularmente ofensivo para mim é o fato de que Stream não implementa Iterable (apesar de realmente ter Método iterator) e não pode ser usado em um para-cada, apenas com um forEach(). Recomendo lançar correntes em Iterables com (Iterable<T>)stream::iterator. Uma melhor alternativa é usar StreamEx que fixa uma série de problemas de API Stream, incluindo a implementação Iterable.

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

  • Atomicamente a iterar numa 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 de fio quando iterava.

  • Execução paralela (utilizando um fluxo paralelo apropriado). Isto poupa-lhe algumas linhas de código vs usando um ExecutorService, se o seu problema corresponder aos pressupostos de desempenho incorporados em fluxos e separadores.

  • Contentores específicos que, tal como a lista sincronizada, beneficiam de estar no controlo da iteração (embora isto seja em grande medida teórico, a menos que as pessoas possam apresentar mais exemplos)

  • Chamando uma única função mais limpa usando forEach() e um argumento de referência do método (ie, list.forEach (obj::someMethod)). No entanto, tenha em mente os pontos sobre exceções assinaladas, depuração mais difícil e redução do número de expressões idiomáticas que você usa ao escrever código.

Artigos que utilizei para referência:

Editar: parece-se com algumas das propostas originais para lambdas (como http://www.javac.info/closures-v06a.html ([48]} questões que mencionei (ao adicionar suas próprias complicações, é claro).

 401
Author: Aleksandr Dubinsky, 2017-04-12 07:32:04

Ao ler esta pergunta pode-se ter a impressão de que {[[7]} em combinação com expressões lambda é um atalho/substituto para escrever um tradicional para cada laço. Isso não é verdade. Este código do OP:

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

Não édestinado como um atalho para escrita

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

E certamente não deve ser usado desta forma. Em vez disso, pretende - se ser um atalho (embora não seja exactamente o mesmo) para escrever

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

E é como 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);
}

Substituindo o corpo de um loop com uma interface funcional, como nos exemplos acima, torna o seu código mais explícito: Você está dizendo que (1) o corpo do loop não afeta o código circundante e fluxo de controle, e (2) o corpo do loop pode ser substituído com uma implementação diferente da função, sem afetar o código circundante. Não ser capaz de acessar variáveis não finais do escopo exterior não é um deficit of functions / lambdas, it is a feature that distinguishes the semantics of Iterable#forEach from the semantics of a traditional for-each loop. Uma vez que se acostuma com a sintaxe de Iterable#forEach, torna o código mais legível, porque você imediatamente recebe esta informação adicional sobre o código.

Tradicional para cada loops certamente permanecerá boa prática (para evitar o termo " boas práticas") em Java. Mas isso não significa que ... considerado má prática ou mau estilo. É sempre uma boa prática, usar a ferramenta certa para fazer o trabalho, e isso inclui misturar tradicional para cada loops com Iterable#forEach, onde faz sentido.

Uma vez que as desvantagens de Iterable#forEach já foram discutidas neste tópico, aqui estão algumas razões, porque você provavelmente pode querer usar Iterable#forEach:

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

  • Para tornar o seu código mais extensível e sustentável: usando uma função como corpo de um laço permite-lhe substituir esta função por diferentes implementações (ver padrão de estratégia). Você poderia, por exemplo, substituir facilmente a expressão lambda por uma chamada de método, que pode ser substituída por sub-classes:

    joins.forEach(getJoinStrategy());
    

    Então você pode fornecer estratégias padrão usando um enum, que implementa a interface funcional. Isto não só torna o seu código mais extensível, ele também aumenta a manutenção, porque ele descupla a implementação do loop a partir da declaração de loop.

  • Para tornar o código mais debugável (debuggable): Seperating o ciclo de implementação da declaração também pode fazer a depuração mais fácil, porque você poderia ter um especializados de depuração de implementação, que imprime mensagens de debug, sem a necessidade de sobrecarregar o código principal com if(DEBUG)System.out.println(). A implementação de depuração pode, por exemplo, ser um delegar , que decore a implementação efectiva da função.

  • Para otimizar o código crítico de desempenho: Ao contrário de algumas das afirmações neste tópico, Iterable#forEach o {[31] } já oferece um desempenho melhor do que um tradicional para cada ciclo, pelo menos ao usar o ArrayList e ao executar o Hotspot no modo "-cliente". Enquanto este impulso de desempenho é pequeno e insignificante para a maioria dos casos de uso, há situações, onde este desempenho extra pode fazer a diferença. Por exemplo, mantenedores de bibliotecas certamente vão querer avaliar, se algumas de suas implementações de loop existentes devem ser substituídas por Iterable#forEach.

    Para confirmar esta afirmação com factos, fiz alguns micro-benchmarks comPaquímetro . Aqui está o código de teste (o último Paquímetro do git é necessário):

    @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:

    Quando executar com "- client", Iterable#forEach supera o tradicional para o loop sobre um ArrayList, mas ainda é mais lento do que iterar diretamente sobre um array. Ao executar com "- server", o desempenho de todas as abordagens é aproximadamente o mesmo.

  • Para fornecer suporte opcional para a execução em paralelo: já foi dito aqui, que a possibilidade de executar a interface funcional de Iterable#forEach em paralelo, usando fluxos, é, certamente, um aspecto importante. Desde Collection#parallelStream() Não garante, que o laço é realmente executado em paralelo, deve-se considerar esta uma funcionalidade opcional. Ao passar por cima da sua lista com list.parallelStream().forEach(...);, Você diz explicitamente: este loop suporta execução paralela, mas não depende dele. Mais uma vez, trata-se de uma característica e não de um défice!

    Ao mover a decisão de execução paralela para longe da implementação real do loop, você permite otimização opcional do seu código, sem afetar o código o que é bom. Além disso, se a implementação padrão de fluxo paralelo não se adequa às suas necessidades, ninguém está impedindo você de fornecer sua própria implementação. Poderá, por exemplo, fornecer uma colecção optimizada, dependendo do sistema operativo subjacente, do tamanho da colecçã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 que é bom aqui é que a implementação do loop não precisa de saber ou de se preocupar com isto. informacao.
 94
Author: Balder, 2015-01-13 16:58:46

forEach() pode ser implementado para ser mais rápido do que para-cada loop, porque o iterable sabe a melhor maneira de iterar seus elementos, em oposição à maneira iterator padrão. Então a diferença é laço interno ou laço 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 laço para cada laço que requer um monte de andaimes

Iterator iter = list.iterator();
while(iter.hasNext())
    Object next = iter.next();
    do something with `next`
No entanto, também precisamos de ter em conta dois custos gerais, usando forEach(), Um está a fazer o lambda. o outro é invocar o método lambda. Eles provavelmente não são significativos.

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

 11
Author: ZhongYu, 2013-07-11 16:01:45

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

Senti que devia adicionar os meus resultados da iteração de benchmarking. Adoptei uma abordagem muito simples (sem quadros de benchmarking) e avaliei 5 métodos diferentes:

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

O procedimento de ensaio e os 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 desta classe deve ser iterada e ter algum doIt(Integer i) aplicado a todos são membros, cada vez através de um método diferente. na classe principal eu executei o método testado três vezes para aquecer a JVM. Executo então o método de ensaio 1000 vezes somando o tempo necessário para cada método de iteração (utilizando System.nanoTime()). Depois disso, divido essa soma por 1000 e esse é o resultado, 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);

Fiz isto numa CPU principal i5 4, 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

Clássico foreach
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
 8
Author: Assaf, 2014-09-25 05:16:03
Acho que preciso de estender um pouco o meu comentário...

sobre o estilo do paradigma\

Esse é provavelmente o aspecto mais notável. FP tornou-se popular devido ao que você pode obter evitando efeitos colaterais. Não vou aprofundar o que prós\contras você pode obter a partir deste, uma vez que isso não está relacionado com a pergunta. No entanto, direi que a iteração usando iterável.forEach é inspirado pelo FP e é o resultado de trazer mais FP para Java (ironicamente, eu diria que há não há muito uso para o forEach em PF puro, uma vez que ele não faz nada exceto introduzir efeitos colaterais). No final eu diria que é uma questão de gosto\estilo\paradigma que você está escrevendo atualmente.

sobre paralelismo.

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

De acordo com os documentos oficiais sobre Iterable.forEach :

Executa a acção indicada na conteúdo do iterável, os elementos de ordem ocorrem ao iterar, até que todos os elementos tenham sido processado ou a ação lança uma exceção.

... isto é, os documentos deixam bem claro que não haverá paralelismo implícito. Adicionar um seria violação da LSP.

Agora, existem" coleções parallells " que são prometidas em Java 8, mas para trabalhar com aqueles que você precisa para mim mais explícito e colocar algum cuidado extra para usá-los (veja a resposta de mschenk74 para exemplo).

BTW: neste caso Stream.forEach será usado, e não garante que o trabalho real será feito em Paralell (depende da coleção subjacente).

Atualização: pode não ser tão óbvio e um pouco esticado de relance, mas há outra faceta de estilo e perspectiva de legibilidade.

Em primeiro lugar, as forloops velhas e simples são planas e velhas. Todos já os conhecem.

Segundo, e mais importante - você provavelmente quer use Iterable.forEach apenas com um liner lambdas. Se o" corpo " ficar mais pesado - eles tendem a não ser-assim legível. Você tem 2 opções a partir daqui-use classes internas (yuck) ou use forloop velho simples. As pessoas muitas vezes ficam irritadas quando vêem as mesmas coisas (iteratins sobre coleções) sendo feitas vários vays/estilos na mesma base de código, e este parece ser o caso.

Mais uma vez, isto pode ou não ser um problema. Depende das pessoas que trabalham em código.
 3
Author: Eugene Loy, 2013-05-19 18:59:55

Uma das limitações mais funcionais forEach é a falta de suporte de excepções verificadas.

Um possível solução é substituir o terminal forEach por um simples laço foreach:

    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á a lista das perguntas mais populares com outros trabalhos sobre o tratamento de excepções verificado em lambdas e streams:

Função Lambda Java 8 que abre excepções?

Java 8: fluxos Lambda, Filtrar por método com Excepção

Como posso lançar exceções verificadas de dentro de Java 8 streams?

Java 8: tratamento obrigatório das excepções assinaladas nas expressões lambda. Porquê obrigatória, e não facultativa?

 2
Author: Vadzim, 2017-05-23 11:33:26

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

ForEach method takes java.util.funcao.Objeto do consumidor como um argumento, então Ajuda em ter a nossa lógica de negócio em um local separado que você pode reutilizá-lo a qualquer momento.

Veja abaixo o excerto,

  • Aqui criei uma nova classe que irá sobrepor o método accept da classe Consumer., 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);
                }
            });
        }
    }
    
 1
Author: Hardik Patel, 2017-12-23 14:59:03