Un trabajo, varias herramientas
Has recopilado algunos datos - en un ArrayList, un HashSet, un HashMap - y ahora quieres visitar cada elemento. Java te ofrece varias formas de hacerlo, y la que elijas depende de si necesitas el índice, de si necesitas eliminar elementos a mitad del bucle y de si prefieres una sintaxis de método o de bucle.
La buena noticia: cada Collection (lista, conjunto, cola) admite el mismo bucle for mejorado, así que una vez que lo aprendes puedes iterarlas todas de la misma manera.
Lee los dos puntos como "en": "para cada lang en langs". Nunca tocas un índice, así que no hay nada que hacer mal.
El bucle for-each
El for mejorado es la opción por defecto para el típico "haz algo con cada elemento". Se lee con claridad y funciona igual en todos los tipos de colección; aquí un HashSet, que no tiene índices en absoluto:
Una cosa que recordar sobre un HashSet: no tiene un orden definido, así que los elementos pueden imprimirse en cualquier secuencia. El for-each los visita todos exactamente una vez de todos modos.
Cuando necesitas el índice
El for-each te da el elemento pero no su posición. Si realmente necesitas el índice - para numerar líneas o para mirar elementos vecinos - usa un bucle contado con size() y get(i). Esto funciona en una List, que es posicional; los conjuntos y mapas no tienen índice, así que este estilo no se les aplica.
No recurras a esto solo por costumbre. Si no usas i para nada más que get(i), la versión con for-each es más corta y más difícil de equivocar.
Iterar un Map
Un Map no es una Collection, así que no puedes hacer for-each directamente sobre él. En su lugar, recorres una de sus tres vistas. La más común es entrySet(), que te entrega cada par clave-valor junto:
Si solo necesitas las claves, recorre ages.keySet(); si solo necesitas los valores, recorre ages.values(). Prefiere entrySet() cuando necesites ambos: recorrer las claves y luego llamar a ages.get(key) dentro hace una segunda búsqueda en cada iteración sin ninguna razón.
El Iterator
El for-each es en realidad azúcar sintáctico sobre un Iterator: un objeto que recorre una colección un elemento a la vez mediante hasNext() y next(). Rara vez escribes este bucle a mano, con una excepción importante: es la forma segura de eliminar elementos mientras iteras.
it.remove() borra el elemento que next() devolvió la última vez, y el iterador sigue siendo válido. Esta es la única forma autorizada de mutar una colección durante un bucle manual.
La trampa: ConcurrentModificationException
Si llamas a add o remove sobre la propia colección dentro de un bucle for-each, obtienes una ConcurrentModificationException: el iterador se da cuenta de que la colección cambió bajo sus pies y se niega a continuar. Este es uno de los errores de principiante más comunes.
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4));
for (int n : nums) {
if (n % 2 == 0) {
nums.remove(Integer.valueOf(n)); // throws ConcurrentModificationException
}
}
La solución es casi siempre removeIf, que expresa la intención en una sola línea y se encarga de la iteración por ti:
removeIf funciona en cualquier Collection, así que la misma llamada también limpia un HashSet.
El método forEach
Cada colección tiene además un método forEach que recibe una lambda y la ejecuta sobre cada elemento. Es una alternativa más funcional, de estilo expresión, al bucle, práctica para frases cortas de una línea:
Fíjate en que Map.forEach recibe directamente una lambda de dos argumentos (key, value) - sin necesidad de entrySet(). Usa forEach para efectos secundarios rápidos; vuelve al bucle for normal cuando el cuerpo crece o necesitas hacer break para salir antes, algo que una lambda no puede hacer.
Siguiente: Métodos
Ya has empaquetado datos en colecciones y los has recorrido de todas las formas que ofrece Java. El siguiente paso es empaquetar comportamiento: escribir tus propios métodos para poder dar nombre a un bloque de lógica, pasarle entradas y reutilizarlo, que es la siguiente página.
Preguntas frecuentes
¿Cómo se recorre una lista en Java?
La forma más limpia es el bucle for mejorado (for-each): for (String s : list) { ... }. Funciona con cualquier Collection: ArrayList, HashSet, etc. Usa un bucle con índice y get(i) solo cuando realmente necesites la posición, y un Iterator cuando necesites eliminar elementos durante el bucle.
¿Cómo se itera sobre un HashMap en Java?
Un Map no es una Collection, así que se recorre una de sus vistas. La opción habitual es for (Map.Entry<K, V> e : map.entrySet()), que te da tanto la clave (e.getKey()) como el valor (e.getValue()) en una sola pasada. También puedes recorrer map.keySet() para las claves o map.values() para los valores.
¿Por qué obtengo una ConcurrentModificationException al iterar?
Llamaste a add o remove sobre la colección mientras un bucle for-each la estaba iterando. El bucle for-each usa un Iterator por debajo y detecta que la colección cambió de forma estructural. Soluciónalo eliminando mediante el método remove() del propio Iterator, o llamando a removeIf(...) en lugar de recorrerla.