Introdução: por que o for-each se tornou o "rei" dos loops em Java?
\n\nSe você programou em Java por pelo menos uma semana, certamente já encontrou a construção for (Type var : collection). Essa sintaxe concisa é chamada de loop for-each (ou enhanced for loop). Ele surgiu no Java 5 (2004) e rapidamente se tornou o favorito dos desenvolvedores.
Mas você sabe o que acontece por baixo dos panos quando a JVM encontra essa construção? Por que o for-each pode ser mais lento que o for comum para arrays? E por que ele às vezes lança ConcurrentModificationException, mesmo que você não esteja removendo nada?
Neste artigo, vamos analisar como funciona o for-each em Java no nível do bytecode, compará-lo com loops clássicos e aprender a evitar armadilhas comuns. Vamos lá!
\n\nSintaxe e primeiros passos
\n\nA sintaxe básica é a seguinte:
\n\nfor (TipoElemento variavel : colecao_ou_array) {\n // corpo do loop\n}\n\nExemplo com ArrayList:
List<String> nomes = Arrays.asList("Ana", "Bruno", "Carlos");\n\nfor (String nome : nomes) {\n System.out.println("Olá, " + nome + "!");\n}\n\nSaída:
\n\nOlá, Ana!\nOlá, Bruno!\nOlá, Carlos!\n\nSimples, certo? Sim. Mas por trás dessa simplicidade, existem dois mecanismos completamente diferentes — para arrays e para coleções.
\n\nComo o for-each funciona com arrays?
\n\nQuando você escreve um for-each para um array, o compilador Java o transforma em um for comum com índice. Sem mágica.
Seu código:
\n\nint[] numeros = {10, 20, 30};\n\nfor (int num : numeros) {\n System.out.println(num);\n}\n\nApós a compilação (descompile o bytecode — confira):
\n\nint[] numeros = new int[]{10, 20, 30};\nfor (int i = 0; i < numeros.length; i++) {\n int num = numeros[i];\n System.out.println(num);\n}\n\nConclusão importante: para arrays, o for-each e o for comum são equivalentes em desempenho. Sem custos adicionais com iteradores.
\n\nComo o for-each funciona com coleções (List, Set)?
\n\nAqui é onde a coisa fica interessante. Para coleções, o for-each usa um iterador.
\n\nSeu código:
\n\nList<String> lista = new ArrayList<>();\nlista.add("Java");\nlista.add("Python");\nlista.add("C++");\n\nfor (String linguagem : lista) {\n System.out.println(linguagem);\n}\n\nO compilador transforma isso em:
\n\nList<String> lista = new ArrayList<>();\nlista.add("Java");\nlista.add("Python");\nlista.add("C++");\n\nfor (Iterator<String> it = lista.iterator(); it.hasNext(); ) {\n String linguagem = it.next();\n System.out.println(linguagem);\n}\n\nOu seja:
\n- \n
- Chama-se
lista.iterator()— obtém-se o iterador. \n - Na condição —
it.hasNext(). \n - No corpo —
it.next(). \n - A variável
linguagemé inicializada com o resultado denext(). \n
O que isso significa na prática?
\n- \n
- Não é possível remover elementos dentro do for-each usando
lista.remove()— você receberá umConcurrentModificationException. Useit.remove()ou um for comum. \n - Não é possível modificar a coleção (adicionar/remover) durante a iteração — o mesmo erro. \n
- Desempenho: para
ArrayList, a diferença para o for comum é mínima (criação do objeto iterador). ParaLinkedList— o for-each é muito mais rápido quefor (int i = 0; i < lista.size(); i++), porqueget(i)em LinkedList é O(n) a cada chamada! \n
E quanto ao Map? Iteração por pares chave-valor
\n\nMap não implementa Iterable diretamente, portanto o for-each