Saltar a contenido

7. Iteración: for-each (enhanced for)

UD3 — Control de Flujo y Depuración · RA3 — Escribe y depura código, analizando y utilizando las estructuras de control del lenguaje

Aspecto Valor
Resultado de aprendizaje RA: 3 — Escribe y depura código, analizando y utilizando las estructuras de control del lenguaje.
Criterios de evaluación a los que contribuye esta sección CE 3.2 (utiliza estructuras de repetición)
CE 3.5 (crea programas ejecutables combinando estructuras de control)
CE 3.7 (comenta y documenta el código).
Marco normativo RD 405/2023 · RD 450/2010 · RD 659/2023
Tecnología base Java 25 LTS (OpenJDK Temurin 25) — soporte hasta 2030+
Horas estimadas ~2 h (de las 30 h de la UD3).

Requisitos previos (lo que ya sabes): condiciones booleanas y operadores lógicos (sección 1 de UD3); if / else if / else, early return, validación con IO.readln y if (sección 2 de UD3); switch expression con flecha y múltiples etiquetas (sección 3 de UD3); while y do-while (sección 4 de UD3); arrays unidimensionales como tipo compuesto (declaración, inicialización, .length, acceso por índice, valores por defecto, String.toCharArray(), swap) — sección 5 de UD3; for clásico sobre arrays y Strings, incluyendo variantes (decreciente, saltos, múltiples variables) y utilidades de Arrays (copyOf, sort, binarySearch, toString) — sección 6 de UD3. Tipos primitivos, var, String (con .equals(), .equalsIgnoreCase(), .length(), .trim(), .toLowerCase(), .charAt()), text blocks, final para constantes y operador ternario (de UD1).


7.1 ¿De qué va esta sección? — Recorrer sin índice

En la sección 6 aprendiste el for clásico y viste que es la herramienta natural para recorrer arrays. Lo usaste para sumar elementos, buscar el máximo, modificar un array, recorrerlo al revés, saltar posiciones, recorrer dos arrays en paralelo, etc.

Pero hay un patrón muy común en el que el for clásico es más verboso de lo necesario: cuando simplemente quieres recorrer todos los elementos en orden, sin necesidad de índice. Mira este ejemplo de la sección 6:

1
2
3
4
5
6
7
int[] numeros = {10, 20, 30, 40, 50};

int suma = 0;
for (int i = 0; i < numeros.length; i++) {
    suma = suma + numeros[i];
}
IO.println("Suma: " + suma);

Fíjate en lo que has escrito: declaras i, comparas i < numeros.length, incrementas i, y dentro del bloque usas numeros[i]. Toda esa maquinaria (inicialización, condición, actualización, acceso por índice) solo sirve para ir pasando por cada elemento. No estás haciendo nada especial con el índice i: lo usas como un "contador de posición" que en realidad no te interesa.

Java (desde la versión 5, de 2004) tiene una sintaxis más simple para este caso: el for-each (también llamado enhanced for o "for mejorado"). El mismo código, escrito con for-each, queda así:

1
2
3
4
5
6
7
int[] numeros = {10, 20, 30, 40, 50};

int suma = 0;
for (int n : numeros) {
    suma = suma + n;
}
IO.println("Suma: " + suma);

Fíjate: no hay índice. No hay i, no hay i < length, no hay i++. Simplemente dices "para cada int n del array numeros, haz esto". Java se encarga de ir pasando al siguiente elemento por ti.

En esta sección vas a aprender el for-each a fondo. Cubriremos, en este orden:

  1. Sintaxis del for-each y su anatomía.
  2. Cómo funciona paso a paso.
  3. Ejemplos básicos: sumar, buscar máximo, contar elementos que cumplen una condición.
  4. for-each con arrays de String.
  5. for-each con caracteres de un String usando toCharArray() (que ya conoces de la sección 5).
  6. for-each vs for clásico: cuándo elegir cada uno (la regla de oro).
  7. La limitación del for-each: no modifica el array (la variable del for-each es una copia).
  8. Trampas clásicas del for-each.
  9. Ejemplos resueltos paso a paso.

Analogía del repartidor de folletos

Imagina que tienes que repartir folletos a 50 personas que están en una cola. Con for clásico, sería como si gritaras: "¡Número 0, toma un folleto! ¡Número 1, toma un folleto! ¡Número 2, toma un folleto!". Tienes que llevar la cuenta del número en todo momento. Con for-each, es como si simplemente fueras caminando por la cola y le dieras un folleto a cada persona, sin importar su número. Mucho más natural, mucho menos que pensar.

Vamos a empezar por la sintaxis, que es lo más simple del mundo.


7.2 Sintaxis del for-each

7.2.1 Anatomía

La sintaxis del for-each es así:

1
2
3
4
for (Tipo elemento : array) {
    // Bloque que se ejecuta una vez por cada elemento
    // 'elemento' contiene el valor actual en cada iteración
}

Partes:

  • Tipo elemento: declaras una variable local del tipo de los elementos que vas a recorrer. Esta variable existe solo dentro del bucle y en cada iteración toma el valor del siguiente elemento.
  • : (dos puntos): se lee como "en" o "de". for (int n : numeros) se lee "para cada n en numeros" o "para cada n de numeros".
  • array: el array que vas a recorrer (en UD6 verás que también funciona con colecciones).
  • { ... }: el bloque que se ejecuta una vez por cada elemento. Dentro del bloque, elemento contiene el valor actual.

7.2.2 Cómo funciona paso a paso

Vamos a verlo con un ejemplo pequeño:

1
2
3
4
5
int[] numeros = {10, 20, 30};

for (int n : numeros) {
    IO.println("El valor actual es: " + n);
}

Salida:

1
2
3
El valor actual es: 10
El valor actual es: 20
El valor actual es: 30

Paso a paso:

  1. Java toma numeros[0] (que es 10), lo asigna a n, y ejecuta el bloque. Se imprime "El valor actual es: 10".
  2. Java toma numeros[1] (que es 20), lo asigna a n, y ejecuta el bloque. Se imprime "El valor actual es: 20".
  3. Java toma numeros[2] (que es 30), lo asigna a n, y ejecuta el bloque. Se imprime "El valor actual es: 30".
  4. Java se da cuenta de que no hay más elementos. Sale del bucle.

Fíjate: no hay índice. Tú no controlas i, no hay i++, no hay i < length. Java hace todo ese trabajo por ti y te entrega cada elemento listo para usar.

7.2.3 Lectura recomendada

La convención para leer el for-each en voz alta es:

"Para cada elemento de array, haz..."

Ejemplo:

1
2
3
for (String nombre : nombres) {
    IO.println("Hola, " + nombre);
}

Se lee: "Para cada nombre de nombres, imprime 'Hola, ' más el nombre". Es prácticamente prosa.

7.2.4 Diagrama de flujo

flowchart TD
    Inicio([Inicio]) --> Init["Prepara el primer elemento"]
    Init --> Condicion{"¿Hay más\nelementos?"}
    Condicion -->|Sí| Asigna["elemento = siguiente\nelemento del array"]
    Asigna --> Cuerpo["Bloque del bucle\n(usas 'elemento')"]
    Cuerpo --> Condicion
    Condicion -->|No| Fin([Fin del bucle])

Fíjate que la estructura es la misma que un while: comprobar, ejecutar, volver a comprobar. La diferencia es que el for-each oculta toda la maquinaria del índice y te entrega el elemento directamente.

7.2.5 El for-each SIEMPRE recorre todo

Una característica importante: el for-each recorre todos los elementos del principio al final, sin excepción. No puedes:

  • Empezar por el medio.
  • Saltar elementos.
  • Ir de atrás hacia adelante.
  • Parar antes de tiempo (salvo con break, que se ve en la sección 8).

Si necesitas alguna de esas cosas, tienes que usar for clásico o while. El for-each es para "quiero pasar por todos, en orden, sin más".

7.2.6 El for-each con diferentes tipos de arrays

Funciona con cualquier tipo de array. Solo tienes que declarar la variable con el tipo correcto:

int[] enteros = {1, 2, 3};
double[] decimales = {1.5, 2.5, 3.5};
String[] palabras = {"hola", "mundo"};
char[] letras = {'a', 'b', 'c'};
boolean[] flags = {true, false, true};

for (int n : enteros)     { IO.println(n); }
for (double d : decimales) { IO.println(d); }
for (String s : palabras) { IO.println(s); }
for (char c : letras)     { IO.println(c); }
for (boolean f : flags)   { IO.println(f); }

La regla: el tipo de la variable del for-each tiene que coincidir con el tipo de los elementos del array. Si el array es int[], la variable es int. Si es String[], la variable es String. Y así con todos.


7.3 Ejemplo básico: sumar todos los elementos

Vamos con el ejemplo más clásico de todos: sumar los elementos de un array de números.

public class SumarArray {

    public static void main(String[] args) {

        int[] numeros = {10, 20, 30, 40, 50};

        int suma = 0;
        for (int n : numeros) {
            suma = suma + n;
        }

        IO.println("La suma es: " + suma);   // 150
    }
}

Paso a paso:

  1. Declaramos el array numeros con 5 valores.
  2. Inicializamos suma = 0 (el acumulador).
  3. Para cada n en numeros:
  4. Iteración 1: n = 10, suma = 0 + 10 = 10.
  5. Iteración 2: n = 20, suma = 10 + 20 = 30.
  6. Iteración 3: n = 30, suma = 30 + 30 = 60.
  7. Iteración 4: n = 40, suma = 60 + 40 = 100.
  8. Iteración 5: n = 50, suma = 100 + 50 = 150.
  9. Salida: "La suma es: 150".

Detalles importantes:

  • suma se declara fuera del bucle. Si la declararas dentro, se reiniciaría en cada iteración y al final tendrías solo el último valor.
  • n se declara dentro del for-each. En cada iteración, n toma un valor nuevo; al terminar el bucle, n deja de existir.
  • El acumulador empieza en 0 (para sumar) o en 1 (para multiplicar). Es el patrón "acumulador" que ya viste en la sección 6.

7.3.1 Patrón general: acumulador con for-each

El patrón "recorrer y acumular" es uno de los más comunes en programación. La estructura es siempre la misma:

1
2
3
4
5
6
// Patrón acumulador con for-each
TipoResultado acumulador = valorInicial;
for (TipoElemento elemento : array) {
    acumulador = acumulador OP elemento;   // donde OP es +, *, etc.
}
// 'acumulador' contiene el resultado final

Ejemplos de variantes:

// Sumar
int suma = 0;
for (int n : numeros) {
    suma = suma + n;
}

// Multiplicar (¡cuidado: empezar en 1, no en 0!)
int producto = 1;
for (int n : numeros) {
    producto = producto * n;
}

// Contar cuántos cumplen una condición
int contador = 0;
for (int n : numeros) {
    if (n > 25) {
        contador = contador + 1;
    }
}

// Concatenar Strings
String resultado = "";
for (String palabra : palabras) {
    resultado = resultado + palabra + " ";
}

El patrón siempre es: inicializar fuera, modificar dentro. Si inicializas dentro, lo pierdes en cada iteración.


7.4 for-each con arrays de String

El for-each funciona igual con cualquier tipo. Veamos un ejemplo con String[].

7.4.1 Saludar a cada alumno

public class SaludarAlumnos {

    public static void main(String[] args) {

        String[] alumnos = {"Ana", "Juan", "María", "Pedro", "Lucía"};

        for (String nombre : alumnos) {
            IO.println("Hola, " + nombre + ". Bienvenida a DAM.");
        }
    }
}

Salida:

1
2
3
4
5
Hola, Ana. Bienvenida a DAM.
Hola, Juan. Bienvenida a DAM.
Hola, María. Bienvenida a DAM.
Hola, Pedro. Bienvenida a DAM.
Hola, Lucía. Bienvenida a DAM.

7.4.2 Buscar el nombre más largo

public class NombreMasLargo {

    public static void main(String[] args) {

        String[] nombres = {"Ana", "Alejandro", "María", "Maximiliano", "Lucía"};

        String masLargo = nombres[0];   // empezamos por el primero
        for (String nombre : nombres) {
            if (nombre.length() > masLargo.length()) {
                masLargo = nombre;
            }
        }

        IO.println("El nombre más largo es: " + masLargo);   // Maximiliano
    }
}

Detalles:

  • masLargo se inicializa con nombres[0] (el primero). Es un patrón típico de "buscar el máximo".
  • En cada iteración comparamos nombre.length() con masLargo.length(). Si el actual es más largo, lo guardamos.
  • Al final del bucle, masLargo contiene el más largo de todos.

7.4.3 Contar cuántos nombres empiezan por una letra

public class ContarPorInicial {

    public static void main(String[] args) {

        String[] nombres = {"Ana", "Alberto", "María", "Antonio", "Lucía", "Andrés"};

        char inicialBuscada = 'A';
        int contador = 0;

        for (String nombre : nombres) {
            if (!nombre.isEmpty() && nombre.charAt(0) == inicialBuscada) {
                contador = contador + 1;
            }
        }

        IO.println("Hay " + contador + " nombres que empiezan por '" + inicialBuscada + "'.");   // 4
    }
}

Detalles:

  • nombre.charAt(0) devuelve el primer carácter del String. El método charAt se ve en profundidad en UD2; aquí lo usamos como ya lo has hecho.
  • !nombre.isEmpty() es una defensa: si el array tuviera un String vacío "", llamar a charAt(0) lanzaría StringIndexOutOfBoundsException. Comprobando primero que no esté vacío, nos protegemos.

7.5 for-each con caracteres de un String: el truco toCharArray()

Hay un caso especial muy útil: recorrer los caracteres de un String uno a uno. Por ejemplo, contar cuántas vocales tiene una palabra.

El problema: String no se puede recorrer directamente con for-each. No puedes escribir for (char c : "hola") { ... } porque el compilador da error. Un String no es una colección de caracteres desde el punto de vista del for-each.

La solución: el método toCharArray() de String (que ya conoces de la sección 5) devuelve un char[] con todos los caracteres. Y un char[] sí se puede recorrer con for-each.

public class ContarVocales {

    public static void main(String[] args) {

        String texto = "Programación en Java 25";
        int vocales = 0;

        // toCharArray() convierte el String en un char[]
        for (char c : texto.toCharArray()) {
            char l = Character.toLowerCase(c);
            boolean esVocal= l == 'a' || l == 'e' || l == 'i'
                    || l == 'o' || l == 'u'
                    || l == 'á' || l == 'é' || l == 'í'
                    || l == 'ó' || l == 'ú';
            if (esVocal) {
                vocales++;
            }
        }

        IO.println("La palabra '" + texto + "' tiene " + vocales + " vocales.");   // 9
    }
}

Salida:

La palabra 'Programación en Java 25' tiene 9 vocales.

Detalles:

  • texto.toCharArray() devuelve {'P', 'r', 'o', 'g', 'r', 'a', 'm', 'a', 'c', 'i', 'ó', 'n', ' ', 'e', 'n', ' ', 'J', 'a', 'v', 'a', ' ', '2', '5'}. Cada carácter, incluidos los espacios, es un elemento del array.
  • Character.toLowerCase(c) convierte el carácter a minúscula antes de comparar, para no tener que mirar a, A, e, E, etc. por separado. El método Character se ve en UD2; aquí lo usamos como "caja negra".

7.5.1 Otro ejemplo: contar mayúsculas

public class ContarMayusculas {

    public static void main(String[] args) {

        String texto = "DAM Java 25 LTS - OpenJDK Temurin";
        int mayusculas = 0;

        for (char c : texto.toCharArray()) {
            if (c >= 'A' && c <= 'Z') {
                mayusculas = mayusculas + 1;
            }
        }

        IO.println("Mayúsculas: " + mayusculas);   // 13
    }
}

Detalles:

  • c >= 'A' && c <= 'Z' comprueba si c es una mayúscula sin acento. Es la forma "manual" sin usar Character.isUpperCase(c) (que verás en UD2).
  • Este método no cuenta la Ñ ni las vocales acentuadas (Á, É, Í, Ó, Ú, Ü). Para hacerlo bien, lo ideal es Character.isUpperCase(c). Aquí mostramos la versión "manual" para que veas la lógica, no porque sea la mejor.

7.5.2 ¿Por qué toCharArray() y no charAt con for clásico?

En la sección 6 viste que podías recorrer un String con for clásico y charAt(i) sin crear un array nuevo:

1
2
3
4
5
// Sección 6: for clásico con charAt (no crea array nuevo)
for (int i = 0; i < texto.length(); i++) {
    char c = texto.charAt(i);
    // ...
}

Aquí con for-each necesitas toCharArray() porque el for-each no puede recorrer un String directamente (no es un array ni una colección). Tienes que convertirlo primero.

¿Cuál es mejor?

Criterio charAt(i) con for clásico toCharArray() con for-each
¿Crea un array nuevo? No (más eficiente en memoria). Sí (crea un char[] nuevo).
¿Sintaxis? Más verboso (texto.charAt(i)). Más limpio (char c : arr).
¿Permite for-each? No. Sí.
¿Recomendado para... Recorridos simples, eficiencia. Casos donde quieres for-each o modificar el array.

Regla del profesor:

Para recorrer un String con for-each, usa toCharArray(). Si quieres eficiencia máxima o no necesitas for-each, usa charAt(i) con for clásico (sección 6).


7.6 for-each vs for clásico: cuándo elegir cada uno

Esta es la decisión más importante de esta sección. Tienes dos bucles que parecen hacer lo mismo (recorrer un array), pero no son intercambiables. Elegir bien al principio te ahorra bugs y hace el código más legible.

7.6.1 Tabla comparativa

Criterio for clásico for-each
¿Necesitas el índice? Sí, es la opción. No, no lo tienes.
¿Quieres recorrer todo, en orden, sin más? Funciona, pero verboso. Mejor for-each.
¿Quieres saltar elementos? Sí, puedes con i += 2. No, recorre todo.
¿Quieres ir de atrás adelante? Sí, for (int i = n-1; i >= 0; i--). No.
¿Quieres modificar elementos del array? Sí, con arr[i] = .... No, elemento es una copia.
¿Recorres dos arrays en paralelo? Sí, con arr1[i] y arr2[i]. No, necesitarías dos bucles.
¿Necesitas parar antes con break? (sección 8) Sí, con break. Sí, con break.
¿Código más legible para recorrido simple? Más verboso. Más limpio.
¿Riesgo de error off-by-one? (< vs <=) Sí. No, no hay índice.

7.6.2 La regla de oro

"Si no necesitas el índice, usa for-each. Si lo necesitas, usa for clásico."

Esa es toda la decisión. El for-each es menos potente pero más seguro y más legible. El for clásico es más potente pero más propenso a errores (off-by-one, índice mal calculado, etc.).

7.6.3 Ejemplos: cuándo elegir cada uno

Caso 1: Sumar todos los elementos.

// for-each: mejor (no necesitas el índice)
int suma = 0;
for (int n : numeros) {
    suma += n;
}

// for clásico: funciona pero es más verboso
int suma = 0;
for (int i = 0; i < numeros.length; i++) {
    suma += numeros[i];
}

→ for-each.

Caso 2: Imprimir cada elemento con su posición.

// for-each: NO PUEDES (no tienes el índice naturalmente)
// (tendrías que llevar un contador aparte, lo cual es feo)
int i = 0;
for (int n : numeros) {
    IO.println("Posición " + i + ": " + n);
    i++;
}

// for clásico: mejor (tienes el índice natural)
for (int i = 0; i < numeros.length; i++) {
    IO.println("Posición " + i + ": " + numeros[i]);
}

→ for clásico.

Caso 3: Modificar los elementos del array (por ejemplo, multiplicar todos por 2).

1
2
3
4
5
6
7
8
9
// for-each: NO FUNCIONA (n es una copia, no modifica el array)
for (int n : numeros) {
    n = n * 2;   // solo modifica la variable local n, NO el array
}

// for clásico: SÍ funciona
for (int i = 0; i < numeros.length; i++) {
    numeros[i] = numeros[i] * 2;   // modifica el array
}

→ for clásico. (Más sobre esto en 7.7.)

Caso 4: Recorrer solo los elementos en posiciones pares.

1
2
3
4
5
// for-each: NO PUEDES (recorre todos)
// for clásico: SÍ, con i += 2
for (int i = 0; i < numeros.length; i += 2) {
    IO.println(numeros[i]);
}

→ for clásico.

Caso 5: Buscar el máximo de un array.

// for-each: mejor (no necesitas el índice)
int max = numeros[0];
for (int n : numeros) {
    if (n > max) {
        max = n;
    }
}

// for clásico: funciona, pero más verboso
int max = numeros[0];
for (int i = 1; i < numeros.length; i++) {
    if (numeros[i] > max) {
        max = numeros[i];
    }
}

→ for-each.

7.6.4 Resumen mental

flowchart TD
    A["¿Quieres recorrer un array?"] --> B{"¿Necesitas el índice?"}
    B -->|No| C{"¿Solo recorrer en orden\nsin saltar ni invertir?"}
    C -->|Sí| D["**for-each**"]
    C -->|No| E["**for clásico**"]
    B -->|Sí| E
    D --> F["Fin"]
    E --> F

Esta es la decisión más importante de la sección. Si te la aprendes, no te equivocarás nunca al elegir bucle.


7.7 La limitación del for-each: no modificar el array

Esta es la trampa más importante del for-each y la que más bugs provoca. Vamos a verla con detalle.

7.7.1 El problema: elemento es una copia

Cuando escribes for (int n : numeros), en cada iteración Java hace algo parecido a:

int n = numeros[i];   // copia el valor del array a la variable n
// ejecuta el bloque usando n

Es decir: n es una copia del valor, no una referencia al elemento del array. Si modificas n, no modificas el array. Mira:

int[] numeros = {1, 2, 3, 4, 5};

for (int n : numeros) {
    n = n * 2;   // Esto NO modifica el array
}

// Después del bucle, numeros sigue siendo {1, 2, 3, 4, 5}
for (int n : numeros) {
    IO.print(n + " ");   // 1 2 3 4 5
}

Salida:

1 2 3 4 5

El array no ha cambiado. Lo que has hecho es multiplicar por 2 una copia local n que se descarta en cada iteración.

7.7.2 La solución: usar for clásico

Si necesitas modificar los elementos del array, tienes que usar for clásico y acceder por índice (como viste en la sección 6):

int[] numeros = {1, 2, 3, 4, 5};

for (int i = 0; i < numeros.length; i++) {
    numeros[i] = numeros[i] * 2;   // Esto SÍ modifica el array
}

// Después del bucle, numeros es {2, 4, 6, 8, 10}
for (int n : numeros) {
    IO.print(n + " ");
}

Salida:

2 4 6 8 10

Ahora sí ha cambiado, porque en cada iteración modificamos numeros[i], no una copia.

7.7.3 ¿Qué pasa con arrays de String?

Con String, que es inmutable (no se puede cambiar su contenido), no puedes modificar el contenido de ningún elemento. Lo máximo que puedes hacer con for clásico es reemplazar un elemento por otro:

1
2
3
4
5
6
7
8
String[] nombres = {"Ana", "Juan", "María"};

// for clásico: reemplazar
for (int i = 0; i < nombres.length; i++) {
    nombres[i] = nombres[i].toUpperCase();   // reemplaza cada elemento
}

// Ahora nombres = {"ANA", "JUAN", "MARÍA"}

Con for-each, esto no funcionaría porque nombre es una copia de la referencia, y reasignarla no afecta al array:

1
2
3
4
// NO FUNCIONA: nombre es una copia
for (String nombre : nombres) {
    nombre = nombre.toUpperCase();   // no modifica el array
}

7.7.4 Regla mnemotécnica

"for-each lee, for clásico escribe."

Si solo vas a leer los elementos (sumarlos, buscarlos, imprimirlos), for-each. Si vas a modificar los elementos (cambiar valores, reemplazar), for clásico.

7.7.5 Adelanto de UD6: modificación de estructura

Cuando veas las colecciones (List, Set) en UD6, descubrirás otra limitación: no puedes añadir ni eliminar elementos de una colección mientras la recorres con for-each. Java lanza ConcurrentModificationException para protegerte. En los arrays esto no aplica (no se pueden redimensionar), pero la idea es la misma: el for-each es para leer, no para modificar la estructura que recorres. Las colecciones se ven en UD6.


7.8 Errores típicos del for-each

Antes de pasar a los ejemplos, repasamos las trampas más típicas.

7.8.1 pensar que elemento modifica el array

Ya lo vimos en 7.7: for (int n : arr) { n = ...; } no modifica arr. La variable n es una copia.

Solución: si quieres modificar, usa for clásico con arr[i] = ....

7.8.2 declarar el tipo incorrecto

1
2
3
4
5
// ERROR: el array es String[] pero declaras la variable como int
String[] palabras = {"a", "b", "c"};
for (int n : palabras) {   // NO compila: int no es String
    IO.println(n);
}

Solución: el tipo de la variable del for-each tiene que coincidir con el tipo de los elementos del array.

7.8.3 usar for-each cuando necesitas el índice

// Feo: llevar un contador manual dentro de for-each
int i = 0;
for (String s : palabras) {
    IO.println(i + ": " + s);
    i++;
}

// Mejor: for clásico
for (int i = 0; i < palabras.length; i++) {
    IO.println(i + ": " + palabras[i]);
}

Solución: si necesitas el índice, usa for clásico.

7.8.4 recorrer un array vacío

1
2
3
4
5
int[] vacio = {};
for (int n : vacio) {
    IO.println("Esto no se imprime nunca");
}
IO.println("Fin");

Salida:

Fin

Si el array tiene 0 elementos, el for-each no ejecuta el bloque ni una sola vez. No es un error, pero puede sorprenderte si esperabas que imprimiera algo. Es equivalente a un for (int i = 0; i < 0; i++) que tampoco entra.

Solución: si necesitas manejar el caso vacío de forma especial, comprueba array.length == 0 antes del bucle.

7.8.5 recorrer un array null

1
2
3
4
int[] nulo = null;
for (int n : nulo) {   // NullPointerException
    IO.println(n);
}

Si el array es null (no vacío, sino null), el for-each lanza NullPointerException. Lo mismo pasa con for clásico al evaluar nulo.length.

Solución: comprueba null antes:

1
2
3
4
5
if (nulo != null) {
    for (int n : nulo) { ... }
} else {
    IO.println("El array era null");
}

7.8.6 confundir la sintaxis con la del for clásico

1
2
3
4
// ERROR: sintaxis mezclada
for (int n : numeros; n < 10; n++) {   // NO compila
    IO.println(n);
}

El for-each no tiene condición ni actualización. Solo Tipo elemento : array. Si necesitas condición o actualización, usa for clásico.

7.8.7 intentar recorrer un String directamente

1
2
3
4
5
6
7
8
9
// ERROR: String no se puede recorrer con for-each
for (char c : "hola") {   // NO compila
    IO.println(c);
}

// BIEN: usar toCharArray() primero
for (char c : "hola".toCharArray()) {
    IO.println(c);
}

Solución: llama a toCharArray() para convertir el String en un char[] que sí se pueda recorrer.


7.9 Ejemplos resueltos paso a paso

Cinco ejemplos completos que integran todo lo aprendido.

7.9.1 Ejemplo 1: estadísticas de un array de notas

import java.util.Arrays;

public class EstadisticasNotas {

    public static void main(String[] args) {

        double[] notas = {7.5, 8.0, 5.5, 9.0, 6.0, 7.0, 8.5, 4.5, 9.5, 6.5};

        // 1. Suma
        double suma = 0;
        for (double n : notas) {
            suma = suma + n;
        }

        // 2. Media
        double media = suma / notas.length;

        // 3. Máximo y mínimo
        double max = notas[0];
        double min = notas[0];
        for (double n : notas) {
            if (n > max) max = n;
            if (n < min) min = n;
        }

        // 4. Cuántas están por encima de la media
        int superiores = 0;
        for (double n : notas) {
            if (n > media) {
                superiores = superiores + 1;
            }
        }

        // Resultados
        IO.println(String.format("Suma:  %.2f", suma));
        IO.println(String.format("Media: %.2f", media));
        IO.println(String.format("Máx:   %.2f", max));
        IO.println(String.format("Mín:   %.2f", min));
        IO.println("Por encima de la media: " + superiores);
        IO.println("Notas (con Arrays.toString): " + Arrays.toString(notas));
    }
}

Salida:

1
2
3
4
5
6
Suma:  72.00
Media: 7.20
Máx:   9.50
Mín:   4.50
Por encima de la media: 5
Notas (con Arrays.toString): [7.5, 8.0, 5.5, 9.0, 6.0, 7.0, 8.5, 4.5, 9.5, 6.5]

Puntos clave:

  1. Cuatro for-each distintos, cada uno con un propósito. Se podría haber hecho todo en un solo for clásico (más eficiente), pero así el código queda más legible: cada bucle hace una sola cosa.
  2. max y min se inicializan con notas[0], no con 0 ni con Double.MAX_VALUE. Es la forma más segura.
  3. Arrays.toString para imprimir el array de golpe (utilidad de la sección 6).
  4. No hay import para IO (es de java.lang).

7.9.2 Ejemplo 2: buscar un nombre en un array

public class BuscarNombre {

    public static void main(String[] args) {

        String[] clase = {"Ana", "Juan", "María", "Pedro", "Lucía", "Carlos"};

        IO.print("¿A quién buscas? ");
        String buscar = IO.readln().trim();

        boolean encontrado = false;
        for (String nombre : clase) {
            if (nombre.equalsIgnoreCase(buscar)) {
                encontrado = true;
                // Aquí pondríamos 'break' (se ve en la sección 8)
                // para no seguir buscando. Por ahora, el bucle sigue hasta el final.
            }
        }

        if (encontrado) {
            IO.println("Sí, " + buscar + " está en la clase.");
        } else {
            IO.println("No, " + buscar + " no está en la clase.");
        }
    }
}

Puntos clave:

  1. equalsIgnoreCase para no distinguir mayúsculas: si el usuario escribe "ana" o "ANA", encuentra a "Ana".
  2. boolean encontrado = false es una "bandera": la ponemos a true cuando encontramos el nombre.
  3. Cuando veamos break (sección 8), podremos salir del bucle en cuanto encontremos el nombre, sin esperar a recorrer todo el array. Por ahora, el bucle termina solo cuando se acaba el array.
  4. Después del bucle, comprobamos la bandera y damos el mensaje. Es el patrón "buscar y reportar".

7.9.3 Ejemplo 3: contar vocales de una frase

public class ContarVocalesFrase {

    public static void main(String[] args) {

        IO.print("Escribe una frase: ");
        String frase = IO.readln();

        int vocales = 0;
        int consonantes = 0;
        int otros = 0;

        for (char c : frase.toCharArray()) {
            char l = Character.toLowerCase(c);
            boolean esVocal = l == 'a' || l == 'e' || l == 'i' || l == 'o' || l == 'u'
                    || l == 'á' || l == 'é' || l == 'í' || l == 'ó' || l == 'ú';
            boolean esConsonante = (l >= 'a' && l <= 'z') && !esVocal;
            if (esVocal) {
                vocales = vocales + 1;
            } else if (esConsonante(c)) {
                consonantes = consonantes + 1;
            } else {
                otros = otros + 1;   // espacios, números, signos
            }
        }

        IO.println("Vocales:     " + vocales);
        IO.println("Consonantes: " + consonantes);
        IO.println("Otros:       " + otros);
    }
}

Salida con "Hola, mundo":

1
2
3
Vocales:     4
Consonantes: 5
Otros:       2

Puntos clave:

  1. toCharArray() para recorrer los caracteres de un String con for-each.
  2. Tres contadores para tres categorías mutuamente excluyentes.
  3. Variables auxiliares esVocal y esConsonante.
  4. Vocales con acento (á, é, í, ó, ú) están incluidas en esVocal. Es un detalle que mucha gente olvida.

7.9.4 Ejemplo 4: validar que todas las notas son aprobadas

public class TodasAprobadas {

    public static void main(String[] args) {

        double[] notas = {7.5, 8.0, 5.5, 9.0, 6.0};
        final double NOTA_APROBADO = 5.0;

        boolean todasAprobadas = true;
        for (double n : notas) {
            if (n < NOTA_APROBADO) {
                todasAprobadas = false;
                // Con 'break' (sección 8) podríamos parar aquí
            }
        }

        if (todasAprobadas) {
            IO.println("Todas las notas son aprobadas.");
        } else {
            IO.println("Hay al menos una nota suspensa.");
        }
    }
}

Puntos clave:

  1. Patrón "todas cumplen": empiezas asumiendo que todas son true, y si encuentras una sola que no cumple, lo pones a false. Es el patrón inverso al "buscar uno": aquí buscas un contraejemplo.
  2. final double NOTA_APROBADO = 5.0: constante sin magic number.
  3. Con break (sección 8), en cuanto encuentres una suspensa puedes salir del bucle. Por ahora, el bucle termina solo al final del array.

7.9.5 Ejemplo 5: menú con for-each para listar

public class MenuListar {

    public static void main(String[] args) {

        String[] opciones = {"listar", "saludar", "salir"};

        IO.println("=== Menú ===");
        IO.println("Opciones disponibles: ");
        for (String op : opciones) {
            IO.println("  - " + op);
        }

        IO.print("¿Qué quieres hacer? ");
        String accion = IO.readln().trim().toLowerCase();

        switch (accion) {
            case "listar" -> {
                String[] alumnos = {"Ana", "Juan", "María", "Pedro"};
                IO.println("Alumnos de la clase:");
                for (String a : alumnos) {
                    IO.println("  - " + a);
                }
            }
            case "saludar" -> {
                String[] alumnos = {"Ana", "Juan", "María", "Pedro"};
                for (String a : alumnos) {
                    IO.println("Hola, " + a + "!");
                }
            }
            case "salir" -> IO.println("Adiós.");
            default -> IO.println("Opción no reconocida.");
        }
    }
}

Puntos clave:

  1. Combinación de switch (sección 3) y for-each (esta sección): el switch decide qué hacer, y dentro de cada case se usa for-each para recorrer un array.
  2. Cada case declara su propio array alumnos local al bloque. Es legal porque cada bloque tiene su propio ámbito.
  3. Patrón "listar opciones": recorrer un array para mostrarlo al usuario. Muy común en menús.

7.10 Buenas prácticas del for-each

7.10.1 Las 8 reglas del for-each profesional

  1. for-each para leer, for clásico para escribir. Si solo recorres, for-each. Si modificas, for clásico.
  2. El tipo de la variable tiene que coincidir con el del array. int[] → int, String[] → String.
  3. Declara el acumulador fuera del bucle. Si lo declaras dentro, se reinicia en cada iteración.
  4. Inicializa máximos y mínimos con array[0], no con 0 ni con constantes mágicas.
  5. No lleves un contador manual dentro del for-each para imitar el índice: si necesitas el índice, usa for clásico.
  6. Comprueba null antes si hay riesgo de que el array sea null.
  7. Usa toCharArray() para recorrer caracteres de un String con for-each.
  8. Cuando llegues a colecciones (UD6), recuerda que no puedes añadir/eliminar durante el recorrido.

7.10.2 Cuándo refactorizar

Síntoma Refactoriza a
for clásico que solo recorre y usa arr[i] sin tocar i. for-each.
for-each con un contador manual int i = 0; que vas incrementando. for clásico (necesitas el índice).
for-each que intenta modificar elemento. for clásico con arr[i] = ....
for-each sobre un String que da error de compilación. toCharArray().

7.10.3 Comentar el for-each

Los comentarios buenos en un for-each explican qué estás calculando, no el bucle en sí:

// MAL: explica lo obvio
for (int n : numeros) {   // recorre numeros
    suma = suma + n;
}

// BIEN: explica la intención
// Sumamos todas las ventas del trimestre para calcular el bonus
int totalVentas = 0;
for (int venta : ventasTrimestre) {
    totalVentas = totalVentas + venta;
}

7.11 Glosario rápido

Término Significado
for-each Bucle que recorre todos los elementos de un array sin índice. También llamado enhanced for.
enhanced for Nombre alternativo de for-each.
elemento Variable del for-each que en cada iteración toma el valor del siguiente elemento.
iterable Lo que se puede recorrer con for-each: arrays (UD3) o cualquier cosa que implemente Iterable<T> (UD6).
Iterable<T> Interfaz de Java que permite el recorrido con for-each. Se ve en UD6.
toCharArray() Método de String que devuelve un char[] con todos los caracteres. Útil para for-each.
Acumulador Variable que se inicializa fuera del bucle y se modifica dentro para sumar, multiplicar, contar, etc.
ConcurrentModificationException Excepción que lanza Java si modificas la estructura de una colección durante el recorrido (UD6).
Búsqueda lineal Recorrer todos los elementos hasta encontrar el buscado. Es lo que hace for-each cuando buscas.

7.12 Actividades propuestas

Cinco actividades graduadas. Hazlas en orden. Las soluciones se publicarán en Aules.

7.12.1 Actividad 1 — Estadísticas de un array de temperaturas

Objetivo: practicar for-each con varios patrones de acumulación.

Enunciado: Dado un array de temperaturas de una semana:

double[] temps = {18.5, 20.0, 22.5, 19.0, 25.0, 23.5, 21.0};

Calcula con for-each (un bucle por cada métrica):

  1. La suma de todas las temperaturas.
  2. La media (suma / número de elementos).
  3. La temperatura máxima.
  4. La temperatura mínima.
  5. Cuántos días han estado por encima de la media.

Muestra los resultados con String.format("%.2f", valor) para los decimales.

Entrega: archivo EstadisticasTemperaturas.java en el paquete jams.programacion.ud3.

Criterios evaluados: CE 3.2, CE 3.5, CE 3.7.

7.12.2 Actividad 2 — Buscar y contar en un array de nombres

Objetivo: practicar for-each con búsqueda y conteo.

Enunciado: Dado un array de nombres de la clase:

String[] clase = {"Ana", "Pedro", "Lucía", "María", "Carlos",
                  "Sofía", "Antonio", "Elena", "Andrés", "Paula"};

Pide al usuario una letra (una sola, leída con IO.readln().charAt(0)). Recorre el array con for-each y muestra:

  1. Cuántos nombres empiezan por esa letra (sin distinguir mayúsculas/minúsculas).
  2. La lista de esos nombres, uno por línea.

Requisitos:

  • Usa Character.toLowerCase(nombre.charAt(0)) para comparar sin distinguir mayúsculas.
  • Comprueba que el nombre no esté vacío antes de llamar a charAt(0).
  • Si no hay ningún nombre con esa inicial, muestra "No hay nombres que empiecen por 'X'."

Entrega: archivo BuscarPorInicial.java en el paquete jams.programacion.ud3.

Criterios evaluados: CE 3.2, CE 3.5, CE 3.7.

7.12.3 Actividad 3 — Analizador de frase

Objetivo: combinar for-each con toCharArray() y patrones de conteo.

Enunciado: Pide al usuario una frase con IO.readln(). Recórrela con for-each sobre toCharArray() y muestra:

  1. Número total de caracteres (incluyendo espacios).
  2. Número de vocales (incluyendo acentuadas: á, é, í, ó, ú).
  3. Número de consonantes (letras sin acento, sin vocales).
  4. Número de espacios.
  5. Número de dígitos (caracteres del '0' al '9').
  6. Número de "otros" (signos de puntuación, símbolos, etc.).

Requisitos:

  • Usa métodos auxiliares esVocal(char), esConsonante(char), esDigito(char), esEspacio(char) definidos fuera del main (copia la sintaxis del ejemplo 7.9.3).
  • Define una constante final String VOCALES = "aeiouáéíóúAEIOUÁÉÍÓÚ"; y comprueba con VOCALES.indexOf(c) >= 0 en esVocal.

Entrega: archivo AnalizadorFrase.java en el paquete jams.programacion.ud3.

Criterios evaluados: CE 3.2, CE 3.5, CE 3.7.

7.12.4 Actividad 4 — Comparativa for-each vs for clásico

Objetivo: entender cuándo elegir cada bucle.

Enunciado: Dado el array:

int[] datos = {3, 7, 2, 9, 4, 6, 1, 8, 5};

Implementa las dos versiones (for-each y for clásico) de cada tarea y comenta cuál es más adecuada:

  1. Sumar todos los elementos.
  2. Imprimir cada elemento con su índice: "Posición 0: 3", "Posición 1: 7", etc.
  3. Duplicar todos los elementos del array (modificar el propio array).
  4. Imprimir solo los elementos en posiciones pares (0, 2, 4, 6, 8).
  5. Buscar el máximo.

Tareas:

  • Para cada tarea, escribe ambas versiones (for-each y for clásico).
  • Comenta al final (en un comentario de bloque /* ... */): ¿cuál es más adecuada en cada caso y por qué?

Entrega: archivo ComparativaBucles.java en el paquete jams.programacion.ud3, con las 10 versiones (5 tareas × 2 bucles) y la comparativa comentada al final.

Criterios evaluados: CE 3.2, CE 3.5, CE 3.7.

7.12.5 Actividad 5 — Cuestionario de repaso

Responde por escrito:

  1. ¿Cuál es la sintaxis del for-each y cómo se lee en voz alta?
  2. ¿Qué tres cosas NO puedes hacer con for-each que sí puedes con for clásico?
  3. ¿Por qué modificar elemento dentro del for-each no cambia el array?
  4. ¿Cuándo elegirías for-each y cuándo for clásico?
  5. ¿Cómo recorres los caracteres de un String con for-each?
  6. ¿Qué pasa si recorres con for-each un array vacío?
  7. ¿Qué pasa si recorres con for-each un array null?
  8. ¿Con qué otros tipos (además de arrays) funciona el for-each? ¿Cuándo se ven?
  9. ¿Qué es ConcurrentModificationException y cuándo aparece?
  10. Escribe el patrón "buscar el máximo" con for-each.
  11. Escribe el patrón "todas cumplen una condición" con for-each.
  12. Si necesitas el índice dentro de un bucle, ¿qué bucle usas? ¿Por qué no for-each?

Entrega: archivo cuestionario_seccion7.md con tus respuestas.

Criterios evaluados: CE 3.2, CE 3.7.


7.13 Lo que NO hemos visto (y dónde verlo)

Esta sección te ha enseñado el for-each para recorrer arrays sin índice. Hay varias cosas relacionadas que se reservan para secciones/unidades posteriores:

Concepto ¿Por qué no aquí? ¿Dónde se ve?
Colecciones (List, Set, Map) Necesita POO y genéricos. UD6 (Tipos Avanzados de Datos).
Iterable<T> y Iterator<T> Interfaces, genéricos y POO. UD6.
Genéricos (<T>, List<String>) Concepto avanzado de tipos parametrizados. UD6.
ConcurrentModificationException real Necesita colecciones para poder probarla. UD6.
break y continue para salir/saltar iteraciones Sentencias de salto de bucle. Sección 8 de UD3.
Bucles anidados (for dentro de for, tablas, matrices) Combinación de bucles. Sección 8 de UD3.
Matrices bidimensionales int[][] Arrays de arrays. Sección 8 de UD3.
Streams API como alternativa a bucles Forma declarativa de procesar colecciones. UD6.

Cuando llegues a UD6, el for-each será tu herramienta principal para recorrer colecciones. Por eso te lo enseñamos en UD3 con arrays: cuando llegues a UD6, ya lo tendrás interiorizado y solo tendrás que aprender las colecciones.


Cierre de la sección. Llegar hasta aquí es mérito. Llevas ya el for y el for-each en el cinturón, y encima has aprendido a manejar arrays. Eso es mucho: la mitad de la programación imperativa consiste en "recorrer una colección de cosas y hacer algo con cada una". La regla de oro —for-each lee, for clásico escribe— te va a ahorrar más bugs de los que crees. Y cuando llegues a UD6 y conozcas List, Set y demás, el for-each será tu herramienta estrella para recorrerlas, porque ya lo habrás practicado a fondo con arrays. Nos vemos en la sección 8, donde cerramos el bloque de bucles con las sentencias de salto y los bucles anidados sobre matrices bidimensionales. Después, en la sección 9, aprenderás a manejar errores con try-catch-finally.