Por que seu código pode estar lento sem motivo aparente?
\nImagine: você escreveu um algoritmo perfeito, passou em todos os testes unitários, mas em produção a aplicação de repente começa a "ficar lenta". Na maioria das vezes, a culpa não é do algoritmo em si, mas de como você percorre as coleções. Isso é especialmente verdade para o Java Map — uma estrutura de dados presente em 9 de cada 10 projetos corporativos.
\nA pergunta "How do I efficiently iterate over each entry in a Java Map?" no Stack Overflow acumulou dezenas de milhares de visualizações não por acaso. Muitos desenvolvedores, mesmo com 2-3 anos de experiência, usam abordagens não ideais. Vamos entender como percorrer um Map corretamente e por que um método pode ser 10 vezes mais rápido que outro.
\n\nAnatomia do Java Map: o que há dentro dele?
\nAntes de falar sobre iteração, vamos relembrar a estrutura. HashMap é uma tabela hash com buckets. TreeMap é uma árvore rubro-negra. LinkedHashMap é um híbrido com uma lista duplamente encadeada. Cada uma dessas estruturas armazena pares key-value, mas as formas de percorrê-las diferem em velocidade e consumo de memória.
As principais coleções de visualização (view collections) que o Map fornece:
\n- \n
keySet()— conjunto de chaves \n values()— coleção de valores \n entrySet()— conjunto de pares (chave-valor) \n
É através dessas três "janelas" que vamos visualizar os dados.
\n\nMétodo nº 1: entrySet clássico com for-each (o mais popular)
\nEste é um padrão básico, mas muito eficiente. Você obtém acesso tanto à chave quanto ao valor em uma única iteração.
\nMap<String, Integer> map = new HashMap<>();\n// ... preenchimento do map\nfor (Map.Entry<String, Integer> entry : map.entrySet()) {\n String key = entry.getKey();\n Integer value = entry.getValue();\n // sua lógica\n}\nPor que é eficiente? Porque entrySet() retorna uma view (visualização), não uma cópia dos dados. O iterador percorre os nós internos (Node) do HashMap, e cada chamada a next() já fornece tanto a chave quanto o valor. Não há busca adicional por hash.
Quando usar: sempre que precisar de ambos os componentes do par. É o padrão de facto na comunidade Java.
\n\nMétodo nº 2: keySet + get (antipadrão a ser evitado)
\nMuitos iniciantes escrevem assim:
\nfor (String key : map.keySet()) {\n Integer value = map.get(key);\n // lógica\n}\nÀ primeira vista, o código é legível. Mas internamente, HashMap.get() recalcula o hash da chave e percorre a cadeia de colisões a cada vez. Para 100 elementos, isso é imperceptível, mas para 100.000, já é uma degradação séria. No pior caso (muitas colisões), a complexidade passa de O(n) para O(n²).
\nConclusão: se você precisa da chave e do valor — use entrySet(). Se precisar apenas das chaves — keySet() é ideal.
Método nº 3: Iterador com remoção explícita (para mutações durante a iteração)
\nÀs vezes, é necessário remover elementos do Map durante a iteração. O loop for-each lançará uma ConcurrentModificationException. É aqui que o iterador entra em ação:
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();\nwhile (iterator.hasNext()) {\n Map.Entry<String, Integer> entry = iterator.next();\n if (entry.getValue() < 0) {\n iterator.remove(); // remoção segura\n }\n}\nEste método é especialmente útil quando você filtra dados "em tempo real". O iterador funciona em O(1) para cada remoção no HashMap, pois ele conhece o nó atual.
\n\nMétodo nº 4: forEach com lambda (Java 8+)
\nA partir do Java 8, o Map possui seu próprio método forEach:
map.forEach((key, value) -> {\n System.out.println(key + " -> " + value);\n});\nPor baixo dos panos