Saltar a contenido

8. Bucles anidados, matrices y sentencias de salto

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.3 (reconoce las posibilidades de las sentencias de salto)
CE 3.5 (crea programas ejecutables combinando estructuras de control y datos compuestos)
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 ~4 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 condicionales simples (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 (sección 5 de UD3); for clásico sobre arrays y Strings, incluyendo variantes (decreciente, saltos, múltiples variables) y utilidades de Arrays (sección 6 de UD3); for-each (sección 7 de UD3). return dentro de main ya lo usaste en early return (sección 2).


8.1 ¿De qué va esta sección? — Anidar, salir y saltar

Has llegado hasta aquí con tres bucles en el cinturón: while (sección 4), for clásico (sección 5) y for-each (sección 6). Ya sabes decidir cuándo usar cada uno y recorrer arrays unidimensionales sin problemas. Pero hay cuatro cosas que no se pueden hacer bien con un solo bucle:

  1. Recorrer una tabla con filas y columnas. Hasta ahora solo has recorrido "filas" (un array de una dimensión). Si quieres una matriz (filas × columnas), necesitas un bucle dentro de otro: un bucle externo para las filas y un bucle interno para las columnas. Eso son los bucles anidados.

  2. Salir del bucle antes de tiempo. Por ejemplo, cuando estás buscando un elemento y lo encuentras en la posición 3, ¿para qué seguir mirando hasta la posición 99? Querrías salir en cuanto lo veas. Para eso está break.

  3. Saltarte una iteración sin ejecutar lo que queda. Por ejemplo, estás procesando una lista de números y, cuando uno es negativo, lo saltas (no lo sumas, no lo imprimes), pero continúas con el siguiente. Para eso está continue.

  4. Salir de un bucle externo desde dentro del interno. Cuando combinas anidamiento y salida, break solo sale del bucle más cercano. Para salir del externo desde dentro del interno existe el break con etiqueta (labeled break).

Y finalmente hay una quinta sentencia de salto que ya conoces pero que aquí verás en su contexto natural: return dentro de un bucle, que sale del método completo (no solo del bucle).

En esta sección vas a aprender las matrices bidimensionales, los bucles anidados y las cuatro sentencias de salto. Cubriremos, en este orden:

  1. Matrices bidimensionales int[][]: declaración, inicialización, matriz.length y matriz[i].length.
  2. Bucles anidados: un bucle dentro de otro. Tablas, combinaciones, recorridos de matrices.
  3. break: salir del bucle más cercano.
  4. continue: saltar a la siguiente iteración.
  5. return dentro de un bucle: salir del método completo.
  6. break con etiqueta (labeled break): salir de bucles anidados externos.
  7. Cuándo los bucles anidados son legítimos y cuándo son un code smell.
  8. Errores típicos.
  9. Ejemplos resueltos paso a paso.

Analogía de la revisión médica

Imagina que eres un médico haciendo una revisión a un paciente. Recorres una lista de pruebas (un bucle). Cuando una prueba da positivo en algo grave, paras (eso es break): ya no necesitas seguir, tienes el diagnóstico. Si una prueba no se puede hacer porque el paciente no colaboró, te la saltas y pasas a la siguiente (eso es continue): no rompes el flujo, simplemente no la cuentas. Y si, en medio de la revisión, deciden ingresar al paciente, abandonas el método completo de "revisión" (eso es return): todo lo demás deja de importar.

Vamos a empezar por las matrices bidimensionales, que son la estructura natural para los bucles anidados.


8.2 Matrices bidimensionales int[][]

8.2.1 Qué es una matriz bidimensional

Una matriz bidimensional (en Java, int[][] o String[][] etc.) es un array de arrays. Es la forma natural de representar tablas con filas y columnas:

  • Una hoja de cálculo con filas y columnas.
  • Un tablero de ajedrez (8 × 8).
  • Una imagen (píxeles en una rejilla).
  • Una matriz matemática (3 × 3, 4 × 5, etc.).

Visualmente:

1
2
3
matriz[0][0]  matriz[0][1]  matriz[0][2]
matriz[1][0]  matriz[1][1]  matriz[1][2]
matriz[2][0]  matriz[2][1]  matriz[2][2]

matriz[i][j] accede al elemento en la fila i, columna j. Los índices empiezan en 0, igual que en los arrays unidimensionales.

8.2.2 Declarar e inicializar con valores

La forma más simple de crear una matriz es con valores literales por filas:

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

Esto crea una matriz de 3 filas y 3 columnas. Cada { ... } interna es una fila.

Otro ejemplo, una matriz de 2 filas × 4 columnas:

1
2
3
4
int[][] tabla = {
    {10, 20, 30, 40},
    {50, 60, 70, 80}
};

8.2.3 Declarar con tamaño fijo

Si no conoces los valores iniciales, creas la matriz con tamaño fijo usando new:

int[][] matriz = new int[3][4];   // 3 filas × 4 columnas, todos los valores a 0

Esto crea una matriz regular de 3 × 4 con todos los valores inicializados a 0 (valor por defecto de int).

8.2.4 matriz.length y matriz[i].length

Aquí hay un detalle importante:

  • matriz.length es el número de filas (porque una matriz int[][] es un array de int[], y .length devuelve cuántos elementos tiene ese array externo).
  • matriz[i].length es el número de columnas de la fila i (porque matriz[i] es un int[] —un array unidimensional— y su .length es el tamaño de esa fila).
int[][] matriz = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

int filas = matriz.length;          // 3 (número de filas)
int columnasFila0 = matriz[0].length;   // 3 (columnas de la fila 0)
int columnasFila1 = matriz[1].length;   // 3
int columnasFila2 = matriz[2].length;   // 3

En matrices regulares (todas las filas tienen la misma longitud), todas las llamadas matriz[i].length devuelven el mismo número. En matrices dentadas (jagged arrays, donde cada fila tiene distinta longitud), cada fila puede tener su propio .length. Las matrices dentadas se ven en UD6.

8.2.5 Acceso por índice

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

int x = matriz[0][0];   // 1 (primera fila, primera columna)
int y = matriz[1][2];   // 6 (segunda fila, tercera columna)
int z = matriz[2][1];   // 8 (tercera fila, segunda columna)

matriz[0][0] = 100;     // ahora la matriz es {{100, 2, 3}, {4, 5, 6}, {7, 8, 9}}

Errores con los índices:

int x = matriz[3][0];   // ArrayIndexOutOfBoundsException (solo hay filas 0, 1, 2)
int y = matriz[0][5];   // ArrayIndexOutOfBoundsException (solo hay columnas 0, 1, 2)

Igual que en arrays unidimensionales, los índices fuera de rango lanzan ArrayIndexOutOfBoundsException.

8.2.6 Tipos de matrices que puedes crear

Puedes hacer matrices de cualquier tipo, no solo int:

1
2
3
4
5
int[][] enteros = { {1, 2}, {3, 4} };
double[][] decimales = { {1.5, 2.5}, {3.5, 4.5} };
String[][] nombres = { {"Ana", "Juan"}, {"María", "Pedro"} };
char[][] tablero = { {'A', 'B', 'C'}, {'D', 'E', 'F'}, {'G', 'H', 'I'} };
boolean[][] flags = { {true, false}, {false, true} };

La sintaxis es siempre la misma: Tipo[][] nombre = { ... };.

También puedes tener matrices con distintos tamaños en las filas:

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

int[][] matriz = new int[3][];

matriz[0] = new int[5];
matriz[1] = new int[2];
matriz[2] = new int[7];

8.3 Bucles anidados: recorrer matrices y generar combinaciones

8.3.1 ¿Qué es un bucle anidado?

Un bucle anidado es un bucle dentro del bloque de otro bucle. Cada vez que el bucle externo da una iteración, el bucle interno recorre completo.

1
2
3
4
5
for (int i = 0; i < 3; i++) {           // Bucle externo
    for (int j = 0; j < 2; j++) {       // Bucle interno
        IO.println("i=" + i + ", j=" + j);
    }
}

Salida:

1
2
3
4
5
6
i=0, j=0
i=0, j=1
i=1, j=0
i=1, j=1
i=2, j=0
i=2, j=1

Paso a paso:

  • El bucle externo i da 3 iteraciones (i=0, 1, 2).
  • Para cada iteración de i, el bucle interno j da 2 iteraciones (j=0, 1).
  • En total: 3 × 2 = 6 combinaciones.

8.3.2 La tabla de multiplicar

El ejemplo clásico de bucles anidados: imprimir la tabla de multiplicar del 1 al 5.

public class TablaMultiplicar {

    public static void main(String[] args) {

        for (int i = 1; i <= 5; i++) {
            for (int j = 1; j <= 5; j++) {
                int producto = i * j;
                IO.print(producto + "\t");
            }
            IO.println();   // salto de línea al final de cada fila
        }
    }
}

Salida:

1
2
3
4
5
1       2       3       4       5       
2       4       6       8       10      
3       6       9       12      15      
4       8       12      16      20      
5       10      15      20      25      

Detalles:

  • El bucle externo i recorre las filas (1 a 5).
  • El bucle interno j recorre las columnas (1 a 5).
  • \t es un tabulador: hace que las columnas queden alineadas (más o menos).
  • IO.println() al final de cada iteración del externo: salto de línea para la siguiente fila.

8.3.3 Diagrama de flujo de bucles anidados

Use mouse to pan and zoom
flowchart TD
    Inicio([Inicio]) --> ExtInit["Inicializa bucle externo"]
    ExtInit --> ExtCond{"¿Externa: hay más\niteraciones?"}
    ExtCond -->|Sí| IntInit["Inicializa bucle interno"]
    IntInit --> IntCond{"¿Interna: hay más\niteraciones?"}
    IntCond -->|Sí| Cuerpo["Cuerpo del bucle interno\n(usas i y j)"]
    Cuerpo --> IntUpdate["Update del interno"]
    IntUpdate --> IntCond
    IntCond -->|No| ExtUpdate["Update del externo"]
    ExtUpdate --> ExtCond
    ExtCond -->|No| Fin([Fin])

Fíjate: el bucle interno se ejecuta completo por cada iteración del externo. Para cada valor de i, se recorre todo el rango de j.

8.3.4 Recorrer una matriz con dos for clásicos

El patrón más típico: un for externo para las filas, un for interno para las columnas:

public class RecorrerMatriz {

    public static void main(String[] args) {

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

        // Bucle externo: filas (i)
        // Bucle interno: columnas (j)
        for (int i = 0; i < matriz.length; i++) {
            for (int j = 0; j < matriz[i].length; j++) {
                IO.print(matriz[i][j] + " ");
            }
            IO.println();
        }
    }
}

Salida:

1
2
3
1 2 3 
4 5 6 
7 8 9 

Detalles clave:

  • matriz.length es el número de filas (3).
  • matriz[i].length es el número de columnas de la fila i. Si la matriz es regular, todas las filas tienen el mismo número de columnas.
  • matriz[i][j] accede al elemento en la fila i, columna j.

8.3.5 Recorrer una matriz con for-each externo

Si solo vas a leer la matriz (no modificarla), puedes usar for-each para el bucle externo:

1
2
3
4
5
6
7
// for-each externo (filas como int[]), for clásico interno
for (int[] fila : matriz) {
    for (int j = 0; j < fila.length; j++) {
        IO.print(fila[j] + " ");
    }
    IO.println();
}

O incluso ambos con for-each:

1
2
3
4
5
6
7
// for-each externo + for-each interno: elegante para solo lectura
for (int[] fila : matriz) {
    for (int valor : fila) {
        IO.print(valor + " ");
    }
    IO.println();
}

Salida idéntica. Más limpio si solo vas a leer. Pero si necesitas el índice (por ejemplo, para modificar), usa for clásico en ambos.

8.3.6 Combinaciones: todas las parejas de dos arrays

Otro caso típico: tienes dos arrays y quieres todas las combinaciones de un elemento del primero con uno del segundo.

public class Parejas {

    public static void main(String[] args) {

        String[] colores = {"rojo", "verde", "azul"};
        String[] tallas = {"S", "M", "L", "XL"};

        for (String color : colores) {
            for (String talla : tallas) {
                IO.println("Camiseta " + color + " talla " + talla);
            }
        }
    }
}

Salida:

1
2
3
4
5
6
7
Camiseta rojo talla S
Camiseta rojo talla M
Camiseta rojo talla L
Camiseta rojo talla XL
Camiseta verde talla S
...
Camiseta azul talla XL

3 colores × 4 tallas = 12 combinaciones. Los bucles anidados son la forma natural de generar combinaciones.

8.3.7 Sumar todos los elementos de una matriz

public class SumarMatriz {

    public static void main(String[] args) {

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

        int suma = 0;
        for (int[] fila : matriz) {
            for (int valor : fila) {
                suma = suma + valor;
            }
        }

        IO.println("Suma total: " + suma);   // 45
    }
}

Patrón acumulador aplicado a matrices. Fácil: el for-each externo te da cada fila (int[]), el for-each interno te da cada valor de esa fila.

8.3.8 Complejidad de los bucles anidados

Si un bucle hace n iteraciones y el interno hace m, las combinaciones son n × m. Si n = m, son n² iteraciones. Eso crece rápido:

n (externo) m (interno) Iteraciones totales
10 10 100
100 100 10.000
1.000 1.000 1.000.000
10.000 10.000 100.000.000

Un bucle anidado con 10.000 × 10.000 = 100 millones de iteraciones ya empieza a ser lento. Por eso hay que pensar dos veces antes de meter un bucle anidado: ¿hay una forma más eficiente de hacer lo mismo? Muchas veces la hay (usando una tabla hash, por ejemplo, que verás en UD6).


8.4 La sentencia break

8.4.1 Qué hace y dónde se usa

break es la sentencia que sale inmediatamente del bucle más cercano (o del switch más cercano, como ya vimos en la sección 3). Cuando Java ejecuta break;, abandona el bucle en el que está y continúa con la instrucción siguiente al bucle.

Dónde se puede usar:

  • Dentro de while y do-while.
  • Dentro de for clásico y for-each.
  • Dentro de switch (clásico, con :). En el switch moderno con flecha -> no hace falta, porque cada rama es exclusiva.

Dónde NO se puede usar:

  • Sueltito en el medio de un método, fuera de un bucle o switch. Eso da error de compilación.
  • Dentro de un if que no está dentro de un bucle. El break se refiere al bucle envolvente, no al if.

8.4.2 Sintaxis

1
2
3
4
5
6
7
8
while (condicion) {
    // ...
    if (algo) {
        break;   // Sale del while inmediatamente
    }
    // Esto ya no se ejecuta si entraste al break
}
// Aquí continúa el programa después del while

8.4.3 Ejemplo: buscar un elemento y salir en cuanto lo encuentres

Este es el caso de uso más clásico de break: buscar algo y parar en cuanto lo encuentras, sin esperar a recorrer todo.

public class BuscarConBreak {

    public static void main(String[] args) {

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

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

        boolean encontrado = false;
        for (String nombre : clase) {
            if (nombre.equalsIgnoreCase(buscar)) {
                encontrado = true;
                break;   // Ya lo encontramos, no hace falta seguir
            }
        }

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

Paso a paso si buscamos "Lucía":

  1. Iteración 1: nombre = "Ana". No coincide. Continuamos.
  2. Iteración 2: nombre = "Pedro". No coincide. Continuamos.
  3. Iteración 3: nombre = "Lucía". Coincide. encontrado = true. break: salimos del bucle. Las iteraciones 4, 5 y 6 no se ejecutan.
  4. Mensaje: "Sí, Lucía está en la clase."

Sin break, el bucle habría recorrido los 6 nombres aunque ya supiéramos el resultado en la posición 3. Con break, ahorramos tiempo y dejamos clara la intención: "en cuanto lo encuentres, para".

8.4.4 Diagrama de flujo con break

Use mouse to pan and zoom
flowchart TD
    Inicio([Inicio]) --> Init["Preparar bucle"]
    Init --> Condicion{"¿Hay más\niteraciones?"}
    Condicion -->|Sí| Cuerpo["Ejecutar cuerpo del bucle"]
    Cuerpo --> BreakCheck{"¿Se ejecutó\nbreak?"}
    BreakCheck -->|Sí| FinBreak([Salir del bucle])
    BreakCheck -->|No| Condicion
    Condicion -->|No| FinNormal([Fin normal])
    FinBreak --> Sigue([Continúa el programa])
    FinNormal --> Sigue

Hay dos formas de salir de un bucle: por la condición (fin normal) o por break (salida anticipada). En ambos casos, el programa continúa con la instrucción siguiente al bucle.

8.4.5 El patrón while (true) + break

Hay un patrón muy común: hacer un bucle "infinito" a propósito y salir con break cuando se cumpla una condición. La idea es clarificar que la salida es por una condición interna, no por la condición del while.

// Bucle infinito "controlado": solo sale con break
while (true) {
    IO.print("Escribe 'salir' para terminar: ");
    String entrada = IO.readln().trim();
    if (entrada.equalsIgnoreCase("salir")) {
        break;
    }
    IO.println("Has escrito: " + entrada);
}
IO.println("Programa terminado.");

Salida de ejemplo:

1
2
3
4
5
6
Escribe 'salir' para terminar: hola
Has escrito: hola
Escribe 'salir' para terminar: 25
Has escrito: 25
Escribe 'salir' para terminar: salir
Programa terminado.

¿Por qué while (true)? Porque la condición de salida no es una sola: el usuario puede escribir cualquier cosa, y solo una de esas cosas ("salir") debe romper el bucle. Si escribieras while (condicion), tendrías que calcular condicion antes de cada iteración, lo cual es más verboso. Con while (true) + break, la lógica de salida está dentro del bucle, donde tiene sentido.

Cuidado con los bucles infinitos de verdad

while (true) + break es legítimo solo si hay un break alcanzable dentro del bucle. Si olvidas el break, o si la condición para llegar a él nunca se cumple, el programa se queda colgado en un bucle infinito. Siempre que escribas while (true), repásate el código dos veces para asegurarte de que el break se va a alcanzar.

8.4.6 break solo sale del bucle MÁS CERCANO

Esto es importante: si tienes bucles anidados, break solo sale del bucle más cercano, no de todos los que le envuelven. Lo veremos en detalle en la sección 8.7 sobre break con etiqueta.

1
2
3
4
5
6
7
8
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (j == 1) {
            break;   // Solo sale del bucle interno (j), no del externo (i)
        }
        IO.println("i=" + i + ", j=" + j);
    }
}

Salida:

1
2
3
i=0, j=0
i=1, j=0
i=2, j=0

El break solo corta la iteración actual de j, pero el for externo (i) continúa con su siguiente iteración. Si necesitas salir del bucle externo, tienes que usar break con etiqueta (sección 8.7).


8.5 La sentencia continue

8.5.1 Qué hace y dónde se usa

continue es la sentencia que salta a la siguiente iteración del bucle, sin ejecutar lo que queda del bloque en la iteración actual. A diferencia de break, no sale del bucle, solo pasa a la siguiente vuelta.

Diferencia con break:

Sentencia Sale del bucle Pasa a la siguiente iteración
break Sí, inmediatamente. No, el bucle termina.
continue No, el bucle sigue. Sí, sin ejecutar lo que quedaba en la iteración actual.

8.5.2 Sintaxis

1
2
3
4
5
6
7
while (condicion) {
    // ...
    if (algo) {
        continue;   // Salta al inicio de la siguiente iteración
    }
    // Esto NO se ejecuta si entraste al continue
}

8.5.3 Ejemplo: sumar solo los números positivos

public class SumarPositivos {

    public static void main(String[] args) {

        int[] numeros = {5, -3, 8, -2, 7, -1, 4, -6, 10};

        int suma = 0;
        for (int n : numeros) {
            if (n < 0) {
                continue;   // Salta a la siguiente iteración, no suma los negativos
            }
            suma = suma + n;
        }

        IO.println("Suma de los positivos: " + suma);   // 5 + 8 + 7 + 4 + 10 = 34
    }
}

Paso a paso:

  1. n = 5: no es negativo, no entra al continue. suma = 0 + 5 = 5.
  2. n = -3: es negativo, entra al continue. Salta a la siguiente iteración. suma no se modifica.
  3. n = 8: suma = 5 + 8 = 13.
  4. n = -2: entra al continue. Salta.
  5. n = 7: suma = 13 + 7 = 20.
  6. n = -1: salta.
  7. n = 4: suma = 20 + 4 = 24.
  8. n = -6: salta.
  9. n = 10: suma = 24 + 10 = 34.

Salida: 34.

8.5.4 continue en for clásico: la actualización SÍ se ejecuta

En un for clásico, cuando se ejecuta continue, Java sí ejecuta el update (el i++) antes de pasar a la siguiente iteración. Vamos a verlo:

1
2
3
4
5
6
for (int i = 0; i < 5; i++) {
    if (i == 2) {
        continue;   // Salta, pero 'i++' se ejecuta igual
    }
    IO.println("i = " + i);
}

Salida:

1
2
3
4
i = 0
i = 1
i = 3
i = 4

Fíjate: el 2 no se imprime porque saltamos con continue, pero el bucle termina correctamente en i = 5. El i++ se ejecutó después del continue.

8.5.5 continue en while: la actualización NO es automática

En un while, no hay update automático. Si usas continue y la variable de control no se modifica, puedes caer en un bucle infinito:

1
2
3
4
5
6
7
8
9
// PELIGROSO: bucle infinito si 'continue' salta el 'i++'
int i = 0;
while (i < 5) {
    if (i == 2) {
        continue;   // ¡OJO! i nunca se incrementa → bucle infinito
    }
    IO.println("i = " + i);
    i++;   // Esto NO se ejecuta cuando entra el continue
}

Solución: en while, incrementa antes del continue o reorganiza la lógica para que el incremento no dependa del final del bloque.

// BIEN: el incremento va antes del 'continue'
int i = 0;
while (i < 5) {
    if (i == 2) {
        i++;           // incrementa ANTES del continue
        continue;
    }
    IO.println("i = " + i);
    i++;
}

El bucle infinito más típico de continue

Si usas while con continue, repásate SIEMPRE dónde está el incremento. Si el incremento está después del continue, ya sabes que vas a tener un bucle infinito. Es uno de los bugs más difíciles de depurar porque no da error, simplemente el programa se queda colgado.

8.5.6 continue en for-each: no hay problema

En for-each no hay índice, así que no hay riesgo de bucle infinito por continue. Simplemente, salta al siguiente elemento:

1
2
3
4
for (int n : numeros) {
    if (n < 0) continue;
    suma = suma + n;
}

Aquí el continue es seguro: pasa al siguiente elemento sin más.

8.5.7 Cuándo usar continue (y cuándo no)

continue es útil cuando un subconjunto de iteraciones tiene un tratamiento distinto (saltárselas) y el resto sigue el flujo normal. Ejemplos buenos:

  • Saltar valores negativos al sumar.
  • Saltar líneas en blanco al procesar un archivo (lo verás en UD5).
  • Saltar elementos que no cumplen un filtro.

Cuando NO usar continue: si lo único que hace es evitar un if, probablemente el código es más legible sin continue:

// Con continue (más verboso)
for (int n : numeros) {
    if (n < 0) continue;
    suma = suma + n;
}

// Sin continue, con if invertido (más simple)
for (int n : numeros) {
    if (n >= 0) {
        suma = suma + n;
    }
}

Las dos versiones hacen lo mismo. La segunda (con if invertido) es más legible para casos simples. Usa continue cuando el bloque del bucle es largo y negar la condición haría que el código quedara indentado a la derecha de más. Para casos de una sola línea, prefiere if.


8.6 return dentro de un bucle

8.6.1 return sale del método completo

return ya lo usaste en early return (sección 2). Aquí lo vemos en su contexto natural: dentro de un bucle, sale no solo del bucle, sino del método completo que lo contiene. Es la forma más radical de salir: abandona todo.

En main, return; termina el programa. Cuando veas métodos propios en UD4, return valor; termina el método y devuelve valor al llamador.

8.6.2 Ejemplo: buscar y salir con return

public class BuscarConReturn {

    public static void main(String[] args) {

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

        IO.print("Dame un número del 1 al 10: ");
        int buscar = Integer.parseInt(IO.readln().trim());

        int posicion = -1;
        for (int i = 0; i < numeros.length; i++) {
            if (numeros[i] == buscar) {
                posicion = i;
                break;   // salimos del bucle sin continuar la búsqueda
            }
        }

        if (posicion == -1) {
            IO.println("No está.");
        } else {
            IO.println("Encontrado en la posición " + posicion + ".");
        }
    }
}

Aquí usamos break porque estamos en main y return; terminaría el programa sin imprimir el resultado. Pero cuando veas métodos en UD4, return será la forma natural de "encontré lo que buscaba, devuelvo el resultado y me voy":

1
2
3
4
5
6
7
8
9
// (Métodos se ven en UD4; por ahora, copia la sintaxis)
static int buscar(int[] array, int objetivo) {
    for (int i = 0; i < array.length; i++) {
        if (array[i] == objectif) {
            return i;   // Sale del método completo, no solo del for
        }
    }
    return -1;   // Si llega aquí, no se encontró
}

Ese return i es más claro que un break con variable auxiliar. Cuando llegues a UD4, te acostumbrarás a usarlo.

8.6.3 return vs break vs continue

Sentencia Sale de... Continúa en...
break El bucle más cercano. La instrucción siguiente al bucle.
continue La iteración actual. La siguiente iteración del bucle.
return El método completo. El llamador (donde se llamó al método).

Regla: break y continue afectan al bucle; return afecta al método.

8.6.4 Cuándo usar return en un bucle

return es ideal cuando has encontrado lo que buscabas y ya no necesitas hacer nada más en el método:

  • Buscar el índice de un elemento: cuando lo encuentras, lo devuelves.
  • Comprobar que todos cumplen una condición: si encuentras uno que no, devuelves false inmediatamente.
  • Salir del main cuando se detecta un error fatal: imprimes el error y return; para terminar.
// Comprobar que todos los números son positivos
// Si hay uno negativo, devuelve false inmediatamente
static boolean todosPositivos(int[] array) {
    for (int n : array) {
        if (n < 0) {
            return false;   // basta un contraejemplo
        }
    }
    return true;   // si llega aquí, todos son positivos
}

Este patrón (devolver false en cuanto encuentras un contraejemplo, devolver true al final) es mucho más limpio que usar break con una bandera boolean todosOK = true. Cuando veas UD4, lo usarás mucho.


8.7 Búsqueda en matriz y break con etiqueta

8.7.1 El problema de salir de dos bucles a la vez

Vamos a buscar un valor concreto en una matriz y a salir en cuanto lo encontremos. Aquí surge la pregunta: ¿cómo salgo de los dos bucles a la vez?

public class BuscarEnMatrizBandera {

    public static void main(String[] args) {

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

        int buscar = 5;
        boolean encontrado = false;
        int filaEncontrada = -1;
        int colEncontrada = -1;

        for (int i = 0; i < matriz.length && !encontrado; i++) {
            for (int j = 0; j < matriz[i].length; j++) {
                if (matriz[i][j] == buscar) {
                    encontrado = true;
                    filaEncontrada = i;
                    colEncontrada = j;
                    break;   // Sale del bucle interno
                }
            }
            // El bucle externo termina porque la condición incluye '&& !encontrado'
        }

        if (encontrado) {
            IO.println("Encontrado en (" + filaEncontrada + ", " + colEncontrada + ")");
        } else {
            IO.println("No está.");
        }
    }
}

Salida:

Encontrado en (1, 1)

Detalles clave:

  • El break solo sale del bucle interno (j).
  • Para que el bucle externo también termine, añadimos && !encontrado a la condición del for externo. Así, cuando encontrado sea true, la condición del externo será false y terminará.
  • Es una técnica común: en vez de break etiquetado, usar una bandera boolean en la condición del bucle externo.

8.7.2 break con etiqueta: alternativa directa

Hay otra forma más directa: el break con etiqueta (labeled break).

Sintaxis:

1
2
3
4
5
6
7
8
etiqueta: for (...) {
    for (...) {
        if (algo) {
            break etiqueta;   // Sale del bucle marcado con 'etiqueta'
        }
    }
}
// Continúa aquí, después del bucle externo

Pasos:

  1. Defines una etiqueta antes del bucle externo, seguida de dos puntos: etiqueta:.
  2. Usas break etiqueta; desde dentro del bucle interno (o desde dentro del externo) para salir hasta ese bucle.

Las etiquetas pueden ser cualquier nombre válido (como un identificador). Por convención, se suelen llamar outer, externo, mainLoop o algo descriptivo.

8.7.3 Ejemplo: buscar en matriz con break etiquetado

public class BuscarEnMatrizEtiquetado {

    public static void main(String[] args) {

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

        int buscar = 5;
        int filaEncontrada = -1;
        int colEncontrada = -1;

        busqueda:
        for (int i = 0; i < matriz.length; i++) {
            for (int j = 0; j < matriz[i].length; j++) {
                if (matriz[i][j] == buscar) {
                    filaEncontrada = i;
                    colEncontrada = j;
                    break busqueda;   // Sale de los DOS bucles
                }
            }
        }

        if (filaEncontrada != -1) {
            IO.println("Encontrado en (" + filaEncontrada + ", " + colEncontrada + ")");
        } else {
            IO.println("No está.");
        }
    }
}

Salida:

Encontrado en (1, 1)

Diferencia con la versión de bandera: aquí no necesitamos la bandera encontrado ni la condición && !encontrado. El break busqueda sale directamente de los dos bucles.

8.7.4 ¿Cuándo usar break etiquetado?

break etiquetado es poco habitual en Java. La mayoría de los casos se pueden resolver con:

  • Bandera boolean en la condición del bucle externo (vimos en 8.7.1).
  • return dentro de un método (cuando el código está en un método, verás en UD4).
  • Reestructuración del código (extraer el bucle externo en un método).

Recomendaciones:

Situación Recomendación
Buscar en matriz desde main. break etiquetado o bandera.
Buscar en matriz desde un método propio. return con el resultado.
Código muy anidado (3+ niveles). Refactorizar: extraer métodos (UD4).
Bucles con lógica compleja. Replantear con otra estructura de datos (UD6).

El goto prohibido

Quizás oigas que "el break etiquetado es como un goto". Es una exageración: goto es salto libre (a cualquier punto del programa), y está prohibido en Java. break etiquetado es salto estructurado: solo puede salir de un bucle marcado, no saltar a cualquier sitio. Aun así, muchos programadores lo evitan porque puede dificultar la lectura. Úsalo con moderación.

8.7.5 continue con etiqueta

Existe también continue etiqueta;, que salta a la siguiente iteración del bucle etiquetado (no del interno). Es aún más raro que break etiquetado. Lo mencionamos para que lo reconozcas si lo ves, pero no te recomendamos usarlo.


8.8 Cuándo los bucles anidados son legítimos (y cuándo son un code smell)

8.8.1 Casos legítimos

Los bucles anidados son legítimos y necesarios cuando:

  1. Recorres una estructura bidimensional (matriz, tablero, imagen).
  2. Generas combinaciones de dos conjuntos (parejas de elementos).
  3. Haces una búsqueda lineal en una matriz.
  4. Calculas una tabla (tabla de multiplicar, tabla de probabilidades).
  5. Comparas cada elemento con cada otro (pares, distancias, similitudes).

En todos estos casos, no hay forma mejor que el bucle anidado: el problema mismo es bidimensional.

8.8.2 Code smells (cuándo evitarlos)

Los bucles anidados son un mal olor (code smell) cuando:

  1. n × m es muy grande (más de unos miles). Suele indicar que necesitas una estructura de datos mejor (hash map, set, sorted array — UD6).
  2. El bucle interno busca en un array desordenado. Si ordenas el array o usas una tabla hash, puedes pasar de n² a n o n log n.
  3. El interno solo cuenta o comprueba algo simple. A veces se puede sustituir por una operación de Stream (UD6) o por una utilidad de Arrays (UD6).
  4. Hay 3 o más niveles de anidamiento. Es ilegible. Refactoriza: extrae métodos (UD4).
  5. Estás combinando dos colecciones por una clave común. Eso es un join, y hay estructuras específicas para ello (Map — UD6).

8.8.3 Ejemplo: lo que NO se debe hacer

// MAL: n × m para comprobar si dos arrays tienen un elemento común
//      Esto es O(n*m) = O(n²) si n = m
for (int a : array1) {
    for (int b : array2) {
        if (a == b) {
            IO.println("Tienen un elemento común: " + a);
        }
    }
}

// MEJOR (con Set, se ve en UD6): O(n + m)
// Set<Integer> conjunto = new HashSet<>(array2);
// for (int a : array1) {
//     if (conjunto.contains(a)) {
//         IO.println("Tienen un elemento común: " + a);
//     }
// }

Aquí la versión con Set (UD6) es mucho más eficiente. Pero como Set se ve en UD6, por ahora el bucle anidado es la única forma que conoces. Está bien usarlo; cuando llegues a UD6, recordarás este ejemplo y verás la mejora.


8.9 Errores típicos

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

8.9.1 bucle infinito por continue en while

Ya la vimos en 8.5.5: si el incremento está después del continue, nunca se ejecuta y el bucle se queda colgado.

Solución: incrementa antes del continue, o usa for clásico donde el update es automático.

8.9.2 break que solo sale del interno

1
2
3
4
5
6
7
// BUG: pensabas que break salía de los dos bucles, pero solo sale del interno
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (j == 1) break;   // Solo sale del j, el i sigue
        IO.println(i + ", " + j);
    }
}

Solución: para salir de los dos, usa break etiquetado o bandera en la condición del externo (ver 8.7.1 y 8.7.2).

8.9.3 return donde querías break

1
2
3
4
5
6
7
8
9
// Si esto estuviera en un método propio, el return terminaría el método,
// no solo el bucle. En 'main' termina el programa.
for (int n : numeros) {
    if (n < 0) {
        return;   // Cuidado: termina el método/programa completo
    }
    IO.println(n);
}
IO.println("Esto no se ejecuta si hubo un negativo");

Solución: si solo quieres salir del bucle, usa break. Si quieres salir del método, return.

8.9.4 continue que se confunde con break

1
2
3
4
5
6
7
// BUG: querías salir del bucle, pero 'continue' solo salta la iteración
for (int n : numeros) {
    if (n < 0) {
        continue;   // El bucle sigue, no termina
    }
    IO.println(n);
}

Solución: ten claro qué quieres: break sale, continue salta iteración.

8.9.5 índices mal calculados en bucles anidados

1
2
3
4
5
6
7
8
// BUG: 'i' se reinicia en cada iteración del externo si la declaras dentro
for (int i = 0; i < 3; i++) {
    int j = 0;   // MAL: 'j' se reinicia en cada iteración de 'i'
    while (j < 3) {
        IO.println(i + ", " + j);
        j++;
    }
}

Aquí j se reinicia a 0 en cada iteración de i. El bucle while interno da 3 iteraciones por cada i, así que el resultado es correcto, pero si declaras j dentro del cuerpo del externo y la necesitas mantener entre iteraciones, pierdes su valor. Lo correcto en un for interno es declararla en el propio for:

1
2
3
4
5
6
// BIEN: 'j' es local al for interno, se reinicia en cada iteración del externo (que es lo que quieres)
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        IO.println(i + ", " + j);
    }
}

8.9.6 complejidad oculta

1
2
3
4
5
6
7
8
// MAL: O(n²) para comprobar duplicados
for (int i = 0; i < arr.length; i++) {
    for (int j = i + 1; j < arr.length; j++) {
        if (arr[i] == arr[j]) {
            IO.println("Hay duplicados");
        }
    }
}

Funciona, pero para n = 10.000 hace 50 millones de comparaciones. Solución: cuando veas Set (UD6), podrás hacerlo en n operaciones.

8.9.7 confundir matriz.length con matriz[i].length

1
2
3
4
5
6
7
8
int[][] matriz = new int[3][4];

// MAL: usar matriz.length para columnas
for (int i = 0; i < matriz.length; i++) {
    for (int j = 0; j < matriz.length; j++) {   // ¡Debería ser matriz[i].length!
        // Recorrería 3 columnas en vez de 4
    }
}

Solución: matriz.length = número de filas; matriz[i].length = número de columnas de la fila i.


8.10 Ejemplos resueltos paso a paso

Cinco ejemplos completos que integran todo lo aprendido.

8.10.1 Ejemplo 1: tabla de multiplicar con formato

public class TablaMultiplicarCompleta {

    public static void main(String[] args) {

        final int FILAS = 10;
        final int COLUMNAS = 10;

        // Cabecera
        IO.print(" × \t");
        for (int j = 1; j <= COLUMNAS; j++) {
            IO.print(j + "\t");
        }
        IO.println();

        // Filas
        for (int i = 1; i <= FILAS; i++) {
            IO.print(i + "\t");
            for (int j = 1; j <= COLUMNAS; j++) {
                IO.print((i * j) + "\t");
            }
            IO.println();
        }
    }
}

Salida (recortada):

1
2
3
4
5
 ×      1       2       3       4       5       6       7       8       9       10      
1       1       2       3       4       5       6       7       8       9       10      
2       2       4       6       8       10      12      14      16      18      20      
3       3       6       9       12      15      18      21      24      27      30      
...

Puntos clave:

  1. Cabecera y filas separadas: la cabecera se imprime con un solo for (no anidado); las filas con dos for anidados.
  2. \t para alinear columnas: no es perfecto, pero suficiente para una tabla.
  3. Constantes FILAS y COLUMNAS: si quieres ampliar la tabla a 20×20, cambias una línea.

8.10.2 Ejemplo 2: buscar en matriz con break etiquetado

public class BuscarEnMatriz {

    public static void main(String[] args) {

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

        IO.print("Número a buscar: ");
        int buscar = Integer.parseInt(IO.readln().trim());

        int fila = -1;
        int col = -1;

        buscar:
        for (int i = 0; i < matriz.length; i++) {
            for (int j = 0; j < matriz[i].length; j++) {
                if (matriz[i][j] == buscar) {
                    fila = i;
                    col = j;
                    break buscar;
                }
            }
        }

        if (fila != -1) {
            IO.println("Encontrado en fila " + fila + ", columna " + col);
        } else {
            IO.println("No está en la matriz.");
        }
    }
}

Puntos clave:

  1. Etiqueta buscar: antes del for externo: define el punto al que apunta el break etiquetado.
  2. break buscar; desde dentro del for interno: sale de los dos bucles a la vez.
  3. fila y col se inicializan a -1: si después del bucle siguen en -1, es que no se encontró.

8.10.3 Ejemplo 3: sumar solo los positivos con continue

public class SumarPositivosConContinue {

    public static void main(String[] args) {

        int[] numeros = {5, -3, 8, -2, 7, -1, 4, -6, 10, 0};

        int suma = 0;
        int cuenta = 0;

        for (int n : numeros) {
            if (n <= 0) {
                continue;   // salta negativos y cero
            }
            suma = suma + n;
            cuenta = cuenta + 1;
        }

        IO.println("Suma de los positivos: " + suma);
        IO.println("Cantidad de positivos: " + cuenta);
        if (cuenta > 0) {
            IO.println(String.format("Media de los positivos: %.2f", (double) suma / cuenta));
        }
    }
}

Salida:

1
2
3
Suma de los positivos: 34
Cantidad de positivos: 5
Media de los positivos: 6.80

Puntos clave:

  1. continue para saltar negativos y cero (n <= 0).
  2. Dos acumuladores: suma para la suma, cuenta para contar cuántos positivos hay.
  3. if (cuenta > 0) antes de la media: si no hubiera positivos, evitaríamos dividir por 0.
  4. (double) suma / cuenta: el cast a double asegura división decimal (si no, int/int da int).

8.10.4 Ejemplo 4: menú interactivo con while (true) + break + continue

public class MenuInteractivo {

    public static void main(String[] args) {

        IO.println("=== Calculadora (escribe 'salir' para terminar) ===");

        while (true) {
            IO.print("\nOperación (suma, resta, multiplica, divide, salir): ");
            String op = IO.readln().trim().toLowerCase();

            if (op.equals("salir")) {
                IO.println("Adiós.");
                break;
            }

            if (!op.equals("suma") && !op.equals("resta")
                    && !op.equals("multiplica") && !op.equals("divide")) {
                IO.println("Operación no reconocida. Intenta de nuevo.");
                continue;   // salta la petición de operandos
            }

            IO.print("Operando A: ");
            double a = Double.parseDouble(IO.readln().trim().replace(',', '.'));
            IO.print("Operando B: ");
            double b = Double.parseDouble(IO.readln().trim().replace(',', '.'));

            switch (op) {
                case "suma" -> IO.println("A + B = " + (a + b));
                case "resta" -> IO.println("A - B = " + (a - b));
                case "multiplica" -> IO.println("A * B = " + (a * b));
                case "divide" -> {
                    if (b == 0) {
                        IO.println("Error: no se puede dividir por cero.");
                    } else {
                        IO.println("A / B = " + (a / b));
                    }
                }
                default -> IO.println("No debería llegar aquí.");
            }
        }
    }
}

Puntos clave:

  1. while (true): el bucle se repite indefinidamente.
  2. break al escribir "salir": la única forma de salir.
  3. continue al escribir una operación no reconocida: no pide operandos, vuelve al menú.
  4. switch (de la sección 3) para ejecutar la operación.
  5. Validación de división por cero con un if (la verás con try-catch en la sección 8).

8.10.5 Ejemplo 5: detectar el primer duplicado en un array

Este ejemplo usa break etiquetado para salir de dos bucles a la vez, en una búsqueda de "par con valor repetido".

public class PrimerDuplicado {

    public static void main(String[] args) {

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

        int primerDuplicado = -1;
        int pos1 = -1;
        int pos2 = -1;

        buscaPareja:
        for (int i = 0; i < numeros.length; i++) {
            for (int j = i + 1; j < numeros.length; j++) {
                if (numeros[i] == numeros[j]) {
                    primerDuplicado = numeros[i];
                    pos1 = i;
                    pos2 = j;
                    break buscaPareja;
                }
            }
        }

        if (primerDuplicado != -1) {
            IO.println("Primer duplicado: " + primerDuplicado
                    + " (posiciones " + pos1 + " y " + pos2 + ")");
        } else {
            IO.println("No hay duplicados.");
        }
    }
}

Salida:

Primer duplicado: 8 (posiciones 1 y 6)

Puntos clave:

  1. Bucles anidados con j = i + 1: comprobamos pares (i, j) sin repetir y sin comparar un elemento consigo mismo. Es el patrón típico de "buscar duplicados" o "buscar parejas que cumplan algo".
  2. break buscaPareja; sale de los dos bucles en cuanto encuentra el primer duplicado.
  3. Complejidad O(n²): si n fuera muy grande (millones), habría que usar una estructura más eficiente (Set de UD6). Aquí para 10 elementos es perfectamente válido.

8.11 Buenas prácticas

8.11.1 Las 10 reglas de matrices, bucles anidados y sentencias de salto

  1. matriz.length para filas, matriz[i].length para columnas. No los confundas.
  2. Bucles anidados: solo cuando el problema es bidimensional. Si es solo "combinar dos colecciones", muchas veces hay estructuras mejores (UD6).
  3. break para "ya encontré lo que buscaba". Si tu bucle es una búsqueda, usa break en cuanto lo encuentres.
  4. continue para "saltar esta iteración, pero seguir". Úsalo solo cuando la iteración actual no aporta nada.
  5. return para "ya tengo el resultado, devuélvelo". En métodos (UD4), es más limpio que break con bandera.
  6. while (true) + break es legítimo si la condición de salida no es simple. Pero asegúrate de que el break es alcanzable.
  7. Evita continue en while si el incremento está después. Es la causa nº 1 de bucles infinitos.
  8. break etiquetado como último recurso. Prefiere extraer un método (UD4) o usar bandera en la condición del externo.
  9. No más de 3 niveles de anidamiento. Si llegas ahí, refactoriza.
  10. Cuidado con la complejidad O(n²): si n > 1000, replantea.

8.11.2 Cuándo refactorizar

Síntoma Refactoriza a
3+ niveles de bucles anidados. Extrae métodos (UD4).
while (true) con varias salidas confusas. Reescribe como do-while con condición clara.
break etiquetado para salir de 3+ bucles. Extrae el código a un método y usa return.
Búsqueda lineal en array grande. Set o Map (UD6).
Bucle anidado para buscar duplicados. Set (UD6): O(n) en vez de O(n²).

8.11.3 Comentar el break y el continue

Los break y continue son puntos donde el flujo "salta". El lector del código (incluido tú dentro de 3 meses) agradecerá un comentario que explique por qué:

// MAL: sin comentario, cuesta ver por qué se sale
for (String nombre : clase) {
    if (nombre.equalsIgnoreCase(buscar)) {
        encontrado = true;
        break;
    }
}

// BIEN: el comentario explica la intención
for (String nombre : clase) {
    if (nombre.equalsIgnoreCase(buscar)) {
        encontrado = true;
        break;   // ya lo encontramos, no hace falta seguir mirando
    }
}

Igual con continue:

1
2
3
4
5
6
7
// BIEN: explica por qué se salta
for (int n : numeros) {
    if (n <= 0) {
        continue;   // solo sumamos positivos, saltamos cero y negativos
    }
    suma = suma + n;
}

8.12 Glosario rápido

Término Significado
Matriz bidimensional Estructura de datos de dos dimensiones (filas × columnas), en Java int[][] o Tipo[][].
int[][] Array de arrays de int. Una matriz bidimensional.
matriz.length Número de filas de la matriz.
matriz[i].length Número de columnas de la fila i.
Bucle anidado Bucle dentro del bloque de otro bucle.
Bucle externo / interno En anidamiento, el de fuera y el de dentro respectivamente.
break Sentencia que sale del bucle más cercano.
continue Sentencia que salta a la siguiente iteración del bucle.
return Sentencia que sale del método completo (con o sin valor).
break etiquetado break etiqueta; que sale del bucle marcado con etiqueta.
continue etiquetado continue etiqueta; que salta a la siguiente iteración del bucle marcado. Poco habitual.
Etiqueta Identificador seguido de : que marca un bucle para break/continue etiquetado.
Bucle infinito Bucle que nunca termina porque la condición nunca se hace false (o el break no se alcanza).
while (true) + break Patrón de bucle con salida interna por condición.
Complejidad O(n²) Cuando el algoritmo hace n × n operaciones. Crece rápido.
Code smell Patrón en el código que indica un problema de diseño, aunque funcione.
Búsqueda lineal Buscar elemento por elemento, sin orden ni estructura auxiliar.

8.13 Actividades propuestas

Estas actividades tienen como objetivo integrar y aplicar los contenidos de la sección: matrices bidimensionales, bucles anidados y sentencias de salto.

Están ordenadas de menor a mayor dificultad. En las primeras se trabaja principalmente el recorrido de matrices; después se incorporan decisiones sobre el flujo de los bucles y, finalmente, se combinan varios conceptos.

Importante: salvo que el enunciado lo indique expresamente, debes decidir qué tipo de bucle y qué sentencia de salto resulta más adecuada en cada situación.

Las soluciones se publicarán en Aules.


8.13.1 Actividad 1 — Analizador de un aparcamiento

Objetivo: practicar el recorrido de matrices y el uso correcto de matriz.length y matriz[i].length.

Un aparcamiento está representado mediante una matriz de valores enteros. Cada posición representa una plaza:

  • 0 → plaza libre.
  • 1 → plaza ocupada.
  • 2 → plaza reservada para personas con movilidad reducida.

Utiliza la siguiente matriz:

1
2
3
4
5
6
int[][] aparcamiento = {
    {1, 0, 1, 1, 0, 0},
    {0, 2, 1, 0, 1, 0},
    {1, 1, 0, 0, 0, 1},
    {0, 0, 1, 2, 1, 0}
};

El programa debe mostrar:

  1. El número total de plazas libres.
  2. El número de plazas ocupadas.
  3. El número de plazas reservadas.
  4. La posición de todas las plazas reservadas, indicando fila y columna.
  5. Qué fila tiene más plazas libres.

Requisitos

  • Recorre la matriz utilizando bucles anidados.
  • No supongas que la matriz tiene siempre 4 filas y 6 columnas.
  • Utiliza matriz.length y matriz[i].length.
  • No es necesario modificar la matriz.
  • Los resultados deben calcularse recorriendo los datos, no escribiendo los resultados directamente.

Entrega: AnalizadorAparcamiento.java.

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


8.13.2 Actividad 2 — Control de calidad de productos

Objetivo: practicar continue y utilizarlo para controlar qué elementos de una secuencia deben procesarse.

Una fábrica registra mediante un array las mediciones de temperatura de una máquina durante un turno:

1
2
3
4
double[] temperaturas = {
    21.5, 22.1, -1, 23.4, 24.0, -1,
    25.2, 26.1, 24.8, 23.9, -1, 22.7
};

El valor -1 significa que no se pudo realizar la medición y, por tanto, no debe participar en los cálculos.

El programa debe calcular:

  • Número de mediciones válidas.
  • Temperatura media de las mediciones válidas.
  • Temperatura máxima.
  • Temperatura mínima.
  • Número de mediciones que están fuera del intervalo recomendado [20.0, 25.0].

Requisitos

  • Las mediciones con valor -1 deben ignorarse.
  • Debes utilizar continue para saltar las mediciones que no sean válidas.
  • No debes contar las mediciones descartadas como mediciones realizadas.
  • Si no existe ninguna medición válida, el programa debe mostrar un mensaje apropiado y no intentar calcular una media.

Reflexión

Explica brevemente por qué en este caso continue resulta más apropiado que break.

Entrega: ControlCalidad.java.

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


8.13.3 Actividad 3 — Ruta de un robot

Objetivo: combinar matrices, bucles anidados y break para controlar una búsqueda.

Un robot se mueve por una cuadrícula representada por una matriz:

1
2
3
4
5
6
7
char[][] mapa = {
    {'.', '.', '.', '#', '.', '.'},
    {'.', '#', '.', '#', '.', '.'},
    {'.', '#', '.', '.', '.', '#'},
    {'.', '.', '.', '#', '.', 'S'},
    {'#', '.', '.', '.', '.', '.'}
};

Los caracteres representan:

  • '.' → posición transitable.
  • '#' → obstáculo.
  • 'S' → posición de salida.

El programa debe localizar la salida y mostrar sus coordenadas.

Sin embargo, existe una condición adicional: el robot comienza a explorar desde la esquina superior izquierda y las posiciones se analizan de izquierda a derecha y de arriba abajo.

Cuando se encuentre la salida, no tiene sentido continuar recorriendo el resto del mapa.

Requisitos

  • Utiliza dos bucles anidados.
  • El recorrido debe realizarse en el orden indicado.
  • Cuando encuentres 'S', debes abandonar la búsqueda.
  • No debes seguir recorriendo posiciones después de encontrarla.
  • Si no existiera salida, debe mostrarse un mensaje indicándolo.

Parte adicional

Modifica el programa para contar cuántas posiciones transitables ('.') se han analizado antes de encontrar la salida.

Pregunta para razonar: ¿es suficiente un break normal para resolver el problema? Justifica tu respuesta.

Entrega: RutaRobot.java.

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


8.13.4 Actividad 4 — Detectar una situación de emergencia

Objetivo: decidir entre break, continue y return según el efecto que se desea conseguir.

Un centro de control recibe datos de diferentes sensores:

1
2
3
4
5
6
int[][] sensores = {
    {18, 21, 19, 20},
    {22, -1, 23, 21},
    {19, 20, 85, 22},
    {21, 20, 19, 18}
};

Cada valor representa una medición.

  • Los valores -1 representan una lectura incorrecta y deben ignorarse.
  • Los valores entre 0 y 50 son normales.
  • Un valor superior a 50 representa una situación de emergencia.

El programa debe analizar las mediciones.

Si encuentra una lectura incorrecta, debe ignorarla y continuar con la siguiente.

Si encuentra una emergencia:

  1. Debe mostrar su posición.
  2. Debe mostrar el valor detectado.
  3. Debe dejar de analizar las mediciones.
  4. Debe indicar cuántas mediciones válidas se habían procesado antes de la emergencia.

Requisitos

El alumno debe decidir cómo implementar la salida de los bucles.

Se permite utilizar:

  • una variable de control,
  • break etiquetado,
  • o cualquier otra solución basada exclusivamente en los contenidos de esta sección.

No se debe utilizar return simplemente porque resulte más corto: la solución debe justificar qué parte del programa se pretende abandonar.

Parte de análisis

Entrega también una breve explicación respondiendo:

  1. ¿Dónde utilizarías continue?
  2. ¿Dónde utilizarías break?
  3. ¿Sería apropiado utilizar return? ¿Por qué?
  4. ¿Qué solución consideras más legible?

Entrega: ControlEmergencia.java.

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


8.13.5 Actividad 5 — Simulador de inventario de un almacén

Objetivo: integrar todos los contenidos de la sección en un problema de mayor complejidad y tomar decisiones sobre el control del flujo.

Un almacén está dividido en zonas. Cada zona contiene varias posiciones y se representa mediante una matriz:

1
2
3
4
5
6
int[][] almacen = {
    {12, 5, 0, 8, 15},
    {3, 0, 7, 11, 4},
    {9, 14, 6, 0, 2},
    {5, 8, 13, 10, 1}
};

Cada número indica las unidades almacenadas en una posición. El valor 0 significa que la posición está vacía.

El programa debe ofrecer el siguiente menú:

1
2
3
4
5
6
7
8
=== CONTROL DE ALMACÉN ===

1. Mostrar inventario
2. Mostrar posiciones vacías
3. Calcular unidades almacenadas
4. Buscar un producto
5. Comprobar capacidad
6. Salir

Para la opción 4, el programa considerará que cada posición contiene un número de unidades y el usuario introducirá una cantidad que desea localizar. Debe indicar si existe alguna posición que contenga exactamente esa cantidad y mostrar la primera posición encontrada.

Para la opción 5, el usuario introducirá una cantidad máxima permitida por posición. El programa debe comprobar si existe alguna posición que supere ese límite.

Requisitos

El programa debe:

  • Utilizar una matriz bidimensional.
  • Recorrerla mediante bucles anidados.
  • Utilizar while (true) para gestionar el menú.
  • Permitir abandonar el menú mediante una sentencia de salto apropiada.
  • Ignorar las posiciones vacías cuando corresponda.
  • Detener una búsqueda cuando ya no sea necesario continuar.
  • Evitar recorrer más elementos de los necesarios.
  • Mostrar mensajes claros al usuario.
  • Comentar las decisiones de flujo especialmente cuando se utilice break o continue.

Restricción importante

No se indica qué sentencia (break, continue o return) debes utilizar en cada parte.

Debes escogerla en función de lo que quieras conseguir y justificar al menos dos de tus decisiones.

Ampliación opcional

Añade una opción:

7. Mover mercancía

que permita seleccionar una posición de origen y otra de destino, trasladando las unidades almacenadas.

Debes controlar que:

  • Las posiciones estén dentro de los límites de la matriz.
  • La posición de origen no esté vacía.
  • La posición de destino pueda recibir la mercancía.

Entrega: ControlAlmacen.java.

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


8.13.6 Actividad 6 — Análisis de código: ¿qué hace realmente?

Objetivo: comprobar que se comprende el comportamiento de los bucles anidados y las sentencias de salto, y no únicamente su sintaxis.

Analiza el siguiente código sin ejecutarlo inicialmente:

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

int contador = 0;

externo:
for (int i = 0; i < datos.length; i++) {

    for (int j = 0; j < datos[i].length; j++) {

        if (datos[i][j] < 0) {
            continue;
        }

        if (datos[i][j] == 9) {
            break externo;
        }

        contador++;
    }
}

IO.println(contador);

Responde:

  1. ¿Qué valores visita el programa?
  2. ¿En qué momento se ejecuta continue?
  3. ¿Qué efecto tiene?
  4. ¿En qué momento se ejecuta break externo?
  5. ¿Qué bucles se abandonan?
  6. ¿Qué valor termina teniendo contador?
  7. ¿Qué ocurriría si break externo se sustituyera por break?
  8. Modifica el código para que el 9 no termine la búsqueda, sino que simplemente se ignore.
  9. Modifica nuevamente el código para que al encontrar el 9 termine el método completo.

Finalmente, ejecuta el programa y comprueba tus respuestas.

Entrega: AnalisisFlujo.md.

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


Cierre de la sección. Llegar al final del bloque de iteración es un hito. Tienes en el cinturón cinco herramientas para repetir (while, do-while, for, for-each y la combinación con break/continue/return), más las matrices bidimensionales para problemas con filas y columnas, y ya sabes decidir cuándo usar cada una. Los bucles anidados, aunque parecen una cosa menor, son la base de muchísimos algoritmos: ordenación, búsqueda, procesamiento de imágenes, juegos de tablero... Y el break etiquetado, aunque poco frecuente, te resuelve problemas que de otra forma requerían refactorizar. La regla más importante que te llevas de esta sección es no abusar: los bucles anidados son potentes, pero O(n²) crece rápido. Cuando llegues a UD6 y conozcas Set y Map, vas a poder hacer con O(n) lo que ahora haces con O(n²). Esa es la belleza de saber más estructuras de datos: cada problema tiene SU herramienta. Nos vemos en la sección 9, donde por fin vamos a aprender a manejar errores en vez de dejar que el programa se caiga.