Cuando el índice solo estorba
Un bucle for con contador te da un contador, una condición y un paso de actualización. Pero gran parte del tiempo en realidad no te importa la posición de un elemento: solo quieres hacer algo con cada uno, en orden, de principio a fin. Gestionar un índice para eso es trabajo innecesario, y es justo donde se cuelan los errores de desplazamiento por uno (off-by-one).
El bucle for-each (Java lo llama for mejorado) elimina el contador por completo. Nombras una variable, la apuntas a una colección y el bucle te entrega cada elemento por turnos.
Sintaxis básica
La forma es for (Tipo elemento : coleccion). Lee los dos puntos como la palabra "en":
No hay i, ni scores.length, ni scores[i]. En cada pasada, score es el siguiente elemento. El bucle se ejecuta una vez por elemento y se detiene automáticamente cuando no quedan más: no puedes pasarte del final ni empezar un elemento demasiado pronto.
Recorrer una lista
El mismo bucle funciona con cualquier cosa iterable, lo que incluye List, Set y los demás tipos de colección. El tipo del elemento va antes del nombre de la variable:
Fíjate en que no tuviste que saber ni preocuparte de si langs se respalda con un array, una lista enlazada o cualquier otra cosa: for-each funciona igual en todas ellas. Esa es su verdadera fortaleza: una sintaxis legible para cada colección.
var te ahorra el nombre del tipo
Si el tipo del elemento es largo u obvio, var deja que el compilador lo infiera para que no te repitas:
Sin var, esa variable del bucle sería el trabalenguas Map.Entry<String, Integer>. var lo mantiene legible, y el tipo sigue estando totalmente comprobado en tiempo de compilación: no es un tipo dinámico ni laxo.
La trampa de la modificación
Aquí está la regla que pilla a todo el mundo: no puedes añadir ni eliminar elementos de una colección mientras un bucle for-each la recorre. Hacerlo lanza una ConcurrentModificationException:
List<String> items = new ArrayList<>(List.of("a", "b", "c"));
for (String item : items) {
if (item.equals("b")) {
items.remove(item); // lanza ConcurrentModificationException
}
}
El bucle nota que la lista cambió por debajo y se detiene en lugar de saltarse o repetir elementos en silencio. Para eliminar de forma segura, baja a un Iterator explícito, que tiene un remove() que el bucle conoce:
Un atajo habitual es items.removeIf(item -> item.equals("b")), que hace lo mismo en una sola línea.
Solo lectura, sin reasignar
Otra limitación sutil: asignar a la variable del bucle cambia únicamente la copia local, no la colección. Esto sorprende a quien viene de lenguajes donde la variable del bucle es una referencia viva:
Si necesitas escribir de vuelta en el array, necesitas el índice, y eso significa el clásico bucle for con contador: for (int i = 0; i < nums.length; i++) nums[i] = nums[i] * 10;. Para elementos que son objetos, sí puedes mutar el objeto al que apunta la variable (llamando a un setter, por ejemplo), pero no puedes reemplazarlo dentro de la colección.
break y continue siguen funcionando
Un for-each es un bucle de verdad, así que break y continue se comportan exactamente igual que en cualquier otro sitio: break sale del bucle y continue salta al siguiente elemento:
Esto imprime keep y luego keep: salta "skip" y se detiene en "stop" antes de llegar a "never". Así que no estás obligado a visitar todos los elementos; simplemente renuncias al índice a cambio de un código más limpio.
Siguiente: Arrays
Ya has recorrido arrays varias veces sin detenerte a pensar en qué son realmente: contenedores indexados de tamaño fijo con ese campo .length. La siguiente página vuelve al principio y cubre los arrays como es debido: cómo declararlos, los valores por defecto con los que empiezan y cómo su tamaño fijo se diferencia de un ArrayList que puede crecer.
Preguntas frecuentes
¿Qué es un bucle for-each en Java?
Un bucle for-each (también llamado for mejorado) recorre todos los elementos de un array o una colección sin necesidad de un contador: for (Tipo elemento : coleccion) { ... }. Se lee como "para cada elemento de la colección". Es más limpio que un bucle for con contador cuando solo necesitas cada elemento y nunca el índice.
¿Cuál es la diferencia entre un bucle for y un bucle for-each en Java?
El bucle for clásico usa un contador explícito (for (int i = 0; i < arr.length; i++)), así que controlas el índice y la dirección. El bucle for-each, for (Tipo x : arr), no tiene índice: visita todos los elementos en orden. Usa for-each para recorridos de solo lectura de principio a fin; usa el bucle con contador cuando necesites el índice, quieras saltarte elementos o modificar la estructura de la colección.
¿Por qué mi bucle for-each lanza ConcurrentModificationException?
Porque llamaste a add() o remove() sobre la colección mientras la iterabas con un for-each. El bucle detecta el cambio estructural y lanza la excepción para protegerte de un comportamiento indefinido. Para eliminar elementos de forma segura, usa un Iterator explícito y su método remove(), o recopila los elementos a borrar y elimínalos después del bucle.