Los bucles de la lección 02-02 son obedientes: empiezan, dan todas las vueltas previstas y terminan. Pero el trabajo real casi nunca es tan disciplinado. Cuando buscas el primer préstamo con multa máxima, no tiene sentido seguir revisando los mil siguientes una vez lo has encontrado. Cuando recorres devoluciones pendientes y te topas con una ya pagada, quieres saltártela sin ejecutar el resto del cuerpo. Para esas dos necesidades —salir antes de tiempo y saltar una iteración— Java ofrece break y continue. Son herramientas afiladas: bien usadas hacen el código más rápido y más claro; mal usadas convierten un bucle en un laberinto. Esta lección cubre ambas, su interacción con el switch (que esconde la trampa más clásica del lenguaje), las etiquetas para romper bucles anidados y las alternativas más legibles a las etiquetas.

Contenido

  1. break: abandonar el bucle
  2. continue: saltar a la siguiente iteración
  3. Los dos sobre el mismo bucle: diagrama comparado
  4. La trampa clásica: break dentro de un switch dentro de un bucle
  5. Etiquetas: break etiqueta y continue etiqueta
  6. Alternativas más legibles a las etiquetas
  7. return dentro de un bucle en main
  8. Tabla comparativa: break, continue, return y bandera
  9. Caso completo: recorrer las devoluciones pendientes de BiblioTech
  10. Errores Comunes y Consejos
  11. Ejercicios

  1. break: abandonar el bucle

break termina inmediatamente el bucle más interno que lo contiene. La ejecución continúa en la primera sentencia posterior al bucle; ni se ejecuta el resto del cuerpo, ni se evalúa la condición, ni se hace la actualización.

final double TARIFA_DIARIA = 0.25;
final double MULTA_MAXIMA = 20.0;

for (int dias = 1; dias <= 200; dias++) {

    double multa = dias * TARIFA_DIARIA;

    if (multa >= MULTA_MAXIMA) {
        System.out.printf("El tope se alcanza a los %d dias de retraso.%n", dias);
        break;                     // no tiene sentido seguir hasta 200
    }
}
System.out.println("Busqueda terminada.");

Salida:

El tope se alcanza a los 80 dias de retraso.
Busqueda terminada.

Sin el break, el bucle daría 200 vueltas y encontraría el tope 121 veces. Con él, se detiene en la 80. Este es el patrón de búsqueda con salida temprana, y es la razón de ser principal de break: en cuanto tienes la respuesta, dejar de trabajar.

Compáralo con la alternativa de la lección 02-02, la bandera booleana:

// Con bandera: sigue iterando aunque ya haya encontrado la respuesta
boolean encontrado = false;
int diasTope = 0;

for (int dias = 1; dias <= 200; dias++) {
    if (!encontrado && dias * TARIFA_DIARIA >= MULTA_MAXIMA) {
        diasTope = dias;
        encontrado = true;
    }
}

Funciona, pero hace 200 iteraciones y añade una condición extra a cada una. Cuando la salida temprana es correcta, break es más claro y más eficiente.

  1. continue: saltar a la siguiente iteración

continue no sale del bucle: abandona la iteración actual y salta al siguiente ciclo. Y aquí hay un matiz que hay que tener muy presente:

  • En un for, continue salta a la actualización (i++) y después a la condición.
  • En un while o do-while, salta directamente a la condición. Si la actualización estaba en el cuerpo, después del continue, no se ejecuta: bucle infinito garantizado.
// Recorrer los primeros 10 dias e informar solo de los que generan multa "redonda"
final double TARIFA_DIARIA = 0.25;

for (int dias = 1; dias <= 10; dias++) {

    double multa = dias * TARIFA_DIARIA;

    if (multa != Math.floor(multa)) {
        continue;                      // no es un importe entero: se descarta
    }

    System.out.printf("%2d dias -> %.2f EUR (importe entero)%n", dias, multa);
}

Salida:

 4 dias -> 1,00 EUR (importe entero)
 8 dias -> 2,00 EUR (importe entero)

Y el peligro con while:

// BUCLE INFINITO: el continue salta el i++
int i = 0;
while (i < 10) {
    if (i == 3) {
        continue;      // vuelve a la condicion con i valiendo 3 para siempre
    }
    System.out.println(i);
    i++;
}

La corrección es actualizar antes del continue, o —mejor— usar un for, donde la actualización está en la cabecera y nunca se salta.

continue es el patrón de filtrado: descartar los elementos que no interesan y quedarse con el camino principal sin indentación extra. De hecho es la versión "dentro del bucle" de la cláusula de guarda que viste en la lección 02-01:

// Con if anidado: el trabajo real queda indentado
for (int n = 1; n <= 10; n++) {
    if (esValido(n)) {
        // ... veinte lineas de trabajo aqui dentro
    }
}

// Con continue: el trabajo real queda plano
for (int n = 1; n <= 10; n++) {
    if (!esValido(n)) {
        continue;
    }
    // ... veinte lineas de trabajo, sin indentacion extra
}

  1. Los dos sobre el mismo bucle: diagrama comparado

La diferencia esencial en un solo dibujo. El bucle es el mismo; solo cambia adónde salta la flecha:

flowchart TD
    INI["Inicializacion"] --> COND{"Condicion del bucle"}
    COND -- "false" --> FIN["Sentencia posterior al bucle"]
    COND -- "true" --> A["Primera parte del cuerpo"]
    A --> Q{"Comprobacion interna"}
    Q -- "continue" --> UPD["Actualizacion"]
    Q -- "break" --> FIN
    Q -- "sigue" --> B["Resto del cuerpo"]
    B --> UPD
    UPD --> COND

Léelo así:

  • continue corta hacia la actualización: el bucle vive, la iteración muere.
  • break corta hacia fuera del bucle: el bucle muere, y con él todas las iteraciones que faltaban.

  1. La trampa clásica: break dentro de un switch dentro de un bucle

Esta es, sin exagerar, una de las tres o cuatro trampas más famosas de Java, y produce bugs que cuestan tardes enteras. La regla es simple y despiadada: break afecta al bloque más interno que lo contiene, y un switch cuenta como bloque interno.

Scanner scanner = new Scanner(System.in);

while (true) {

    System.out.print("Opcion (1=consultar, 0=salir): ");
    String opcion = scanner.nextLine().trim();

    switch (opcion) {
        case "1":
            System.out.println("Consultando catalogo...");
            break;                       // sale del switch. Correcto.
        case "0":
            System.out.println("Saliendo...");
            break;                       // ¡sale del SWITCH, no del WHILE!
        default:
            System.out.println("Opcion no valida.");
    }
}

El programador escribió break en el case "0" creyendo que terminaba el programa. Lo que hace es salir del switch y volver al principio del while (true): el menú es imposible de abandonar. El usuario elige 0, ve "Saliendo…" y el menú reaparece. Para siempre.

Hay tres formas correctas de resolverlo:

Solución 1: bandera booleana (la más legible, y la que usa BiblioTech).

boolean salir = false;

while (!salir) {
    String opcion = scanner.nextLine().trim();

    switch (opcion) {
        case "1":
            System.out.println("Consultando catalogo...");
            break;
        case "0":
            System.out.println("Saliendo...");
            salir = true;                // la condicion del while se encarga
            break;
        default:
            System.out.println("Opcion no valida.");
    }
}

Solución 2: switch con flechas, donde nada invita a escribir break porque las ramas ya no lo necesitan para separarse. Conviene ser preciso, porque es un matiz que casi nadie explica bien: si te empeñas y escribes case "0" -> { System.out.println("Saliendo..."); break; }, eso compila y cae exactamente en la misma trampa (sale del switch, no del bucle). Lo que no compila es case "0" -> break; a secas, ni un break dentro de un switch de expresión. La sintaxis de flechas no te protege de la trampa: simplemente hace que no se te ocurra caer en ella.

boolean salir = false;

while (!salir) {
    String opcion = scanner.nextLine().trim();

    switch (opcion) {
        case "1" -> System.out.println("Consultando catalogo...");
        case "0" -> {
            System.out.println("Saliendo...");
            salir = true;
        }
        default  -> System.out.println("Opcion no valida.");
    }
}

Solución 3: etiqueta en el bucle, que es el tema del apartado siguiente.

bucleMenu:
while (true) {
    String opcion = scanner.nextLine().trim();

    switch (opcion) {
        case "1":
            System.out.println("Consultando catalogo...");
            break;
        case "0":
            System.out.println("Saliendo...");
            break bucleMenu;             // rompe el BUCLE, no el switch
        default:
            System.out.println("Opcion no valida.");
    }
}

Un apunte sobre continue: continue no tiene ningún significado dentro de un switch. Si lo escribes ahí, salta la iteración del bucle que envuelve al switch, que suele ser lo que quieres; no hay ambigüedad porque un switch no es un bucle. La trampa es exclusiva de break.

  1. Etiquetas: break etiqueta y continue etiqueta

Una etiqueta es un identificador seguido de dos puntos, colocado justo antes de un bucle. Permite que break y continue actúen sobre un bucle externo, no solo sobre el más interno.

nombreEtiqueta:
for (...) {
    for (...) {
        break nombreEtiqueta;      // sale del bucle EXTERNO
        continue nombreEtiqueta;   // pasa a la siguiente iteracion del EXTERNO
    }
}

Sin etiqueta, break en un bucle anidado solo abandona el interno y el externo continúa como si nada. Ejemplo realista de BiblioTech: buscar, entre varios empleados y sus libros, la primera devolución que ha alcanzado la multa máxima, y parar toda la búsqueda ahí.

package com.nexussoftware.bibliotech;

public class BiblioTechApp {
    public static void main(String[] args) {

        final double TARIFA_DIARIA = 0.25;
        final double MULTA_MAXIMA = 20.0;

        String empleadoEncontrado = "-";
        int libroEncontrado = 0;
        int diasEncontrados = 0;

        // Registro simulado: tres empleados, cuatro libros cada uno.
        // Los dias de retraso se generan con una formula para poder repetir
        // la prueba siempre igual (aun no podemos guardar una lista real).
        busqueda:
        for (int empleado = 1; empleado <= 3; empleado++) {

            for (int libro = 1; libro <= 4; libro++) {

                int diasRetraso = (empleado * 29 + libro * 17) % 130;
                double multa = diasRetraso * TARIFA_DIARIA;

                System.out.printf("Empleado %d, libro %d: %3d dias -> %6.2f EUR%n",
                                  empleado, libro, diasRetraso, multa);

                if (multa >= MULTA_MAXIMA) {
                    empleadoEncontrado = "Empleado " + empleado;
                    libroEncontrado = libro;
                    diasEncontrados = diasRetraso;
                    break busqueda;        // abandona LOS DOS bucles
                }
            }
        }

        System.out.println();
        if (diasEncontrados > 0) {
            System.out.printf("Primera multa topada: %s, libro %d (%d dias).%n",
                              empleadoEncontrado, libroEncontrado, diasEncontrados);
        } else {
            System.out.println("Ningun libro ha alcanzado la multa maxima.");
        }
    }
}

Salida:

Empleado 1, libro 1:  46 dias ->  11,50 EUR
Empleado 1, libro 2:  63 dias ->  15,75 EUR
Empleado 1, libro 3:  80 dias ->  20,00 EUR

Primera multa topada: Empleado 1, libro 3 (80 dias).

Si en lugar de break busqueda; hubieras escrito break;, el bucle interno se habría abandonado, pero el externo habría pasado al empleado 2 y la búsqueda habría continuado, encontrando más resultados de los que querías.

continue etiqueta es menos frecuente, pero tiene su hueco: "esta fila ya no me interesa, pasa a la siguiente".

// Si un empleado tiene algun libro con retraso grave, saltamos al siguiente
// empleado sin revisar sus libros restantes.
porEmpleado:
for (int empleado = 1; empleado <= 3; empleado++) {

    System.out.println("Revisando empleado " + empleado);

    for (int libro = 1; libro <= 4; libro++) {
        int diasRetraso = (empleado * 29 + libro * 17) % 130;

        if (diasRetraso > 60) {
            System.out.println("  Retraso grave detectado. Se marca al empleado.");
            continue porEmpleado;      // salta al siguiente empleado
        }

        System.out.println("  Libro " + libro + " correcto (" + diasRetraso + " dias)");
    }

    System.out.println("  Empleado sin incidencias.");
}

La advertencia obligatoria. Las etiquetas son legales y a veces son la solución más directa, pero su abuso es un olor de diseño (code smell). Un método con tres etiquetas y saltos cruzados es tan difícil de seguir como el goto que los lenguajes estructurados quisieron eliminar. Regla práctica del oficio: si necesitas una etiqueta, primero pregúntate si el bucle interno no debería ser un método aparte. Casi siempre la respuesta es que sí.

  1. Alternativas más legibles a las etiquetas

Alternativa 1: bandera booleana en la condición del bucle externo.

boolean encontrado = false;

for (int empleado = 1; empleado <= 3 && !encontrado; empleado++) {
    for (int libro = 1; libro <= 4 && !encontrado; libro++) {
        if (/* condicion de parada */) {
            encontrado = true;
        }
    }
}

Ventaja: no hay saltos; la condición de salida está a la vista en la cabecera. Inconveniente: la bandera aparece en tres sitios y hay que recordar actualizarla. Para dos niveles es perfectamente legible; para tres, ya no.

Alternativa 2: extraer el bucle interno a un método. Cuando el bucle interno vive en su propio método, un simple return lo abandona por completo y el bucle externo decide qué hacer con el resultado. Es la solución preferida en código profesional, porque además pone nombre a lo que hace ese bucle. No la puedes aplicar todavía —los métodos se estudian en la lección 03-03—, pero tenla presente: cuando llegues allí, gran parte de tus etiquetas desaparecerán.

Alternativa 3: replantear el bucle. Muchas búsquedas anidadas se pueden expresar como un solo bucle sobre un índice combinado, o se simplifican radicalmente cuando dispones de colecciones (módulo 5) y, más adelante, de la API de streams (lección 10-04), donde "encuentra el primero que cumpla X" es una única línea.

  1. return dentro de un bucle en main

return termina el método en el que aparece. Como en este módulo todo tu código vive en main, un return dentro de un bucle termina el programa, saltándose el resto de todo.

for (int dias = 1; dias <= 200; dias++) {
    if (dias * TARIFA_DIARIA >= MULTA_MAXIMA) {
        System.out.println("Tope alcanzado a los " + dias + " dias.");
        return;                   // termina main: el programa acaba aqui
    }
}
System.out.println("Esta linea NO se ejecuta si se ha hecho return.");

Cuidado con una consecuencia poco obvia: si tienes recursos abiertos (como el Scanner), un return prematuro se salta el scanner.close(). En un programa de consola pequeño no es grave —la JVM libera todo al terminar—, pero es un mal hábito que en el módulo 6 corregirás definitivamente con try-with-resources.

Dentro de main, return y break de un bucle exterior parecen equivalentes, pero no lo son: break deja que el programa siga con lo que venga después del bucle (el resumen, el cierre del Scanner, la despedida); return lo mata todo. Usa break para salir de un bucle y reserva return para terminar el programa de verdad.

  1. Tabla comparativa: break, continue, return y bandera

Mecanismo Qué abandona Se ejecuta después Uso típico Riesgo
break El bucle (o switch) más interno La sentencia siguiente al bucle Búsqueda con salida temprana Confundir el switch con el bucle
break etiqueta El bucle etiquetado, con todo lo anidado La sentencia siguiente a ese bucle Búsqueda en bucles anidados Ilegible si se abusa
continue La iteración actual La actualización y la condición Filtrar y descartar elementos Bucle infinito en while
continue etiqueta La iteración actual del bucle etiquetado La actualización del bucle etiquetado Descartar una "fila" entera Muy poco frecuente; sorprende al lector
return El método completo (aquí, main) Nada más del método Terminar el programa Se salta el código de cierre
Bandera booleana Nada: el bucle acaba por su condición El resto del cuerpo (salvo que se proteja) Menús, salidas controladas Verbosa; hay que actualizarla siempre

  1. Caso completo: recorrer las devoluciones pendientes de BiblioTech

Reunimos todo en el ejemplo que da sentido a la lección. Nexus Software quiere revisar la cola de devoluciones pendientes con dos reglas:

  1. Las devoluciones ya pagadas se ignoran (no suman a los totales).
  2. En cuanto aparezca una devolución con multa máxima, se detiene la revisión y se escala el caso a Recursos Humanos.

Como todavía no puedes almacenar una lista, el registro se simula con dos fórmulas deterministas, de modo que la salida sea siempre la misma y puedas comprobarla. Este es el contenido equivalente del registro simulado:

diasRetraso = (n * 37) % 130 pagada = n % 2 == 0 Multa (con tope 20,00 €)
1 37 no 9,25 €
2 74 — (pagada)
3 111 no 20,00 € (topada)
4 18 — (pagada)
5 55 no 13,75 €
6 92 — (pagada)
7 129 no 20,00 € (topada)
8 36 — (pagada)
package com.nexussoftware.bibliotech;

public class BiblioTechApp {
    public static void main(String[] args) {

        final String NOMBRE_EMPRESA = "Nexus Software";
        final double TARIFA_DIARIA = 0.25;
        final double MULTA_MAXIMA = 20.0;
        final int UMBRAL_LEVE = 7;
        final int TOTAL_PENDIENTES = 8;

        // Acumuladores de la revision
        int revisadas = 0;
        int omitidasPorPago = 0;
        double totalPendiente = 0.0;
        boolean escalado = false;
        int devolucionEscalada = 0;

        System.out.println("=== REVISION DE DEVOLUCIONES PENDIENTES ===");
        System.out.println("Empresa: " + NOMBRE_EMPRESA);
        System.out.println();

        for (int n = 1; n <= TOTAL_PENDIENTES; n++) {

            // --- Datos simulados del registro ---
            int diasRetraso = (n * 37) % 130;
            boolean pagada = (n % 2 == 0);

            // --- CONTINUE: filtrar lo que no interesa ---
            if (pagada) {
                omitidasPorPago++;
                System.out.printf("  #%d ... ya pagada, se omite%n", n);
                continue;              // salta al siguiente n, sin tocar los totales
            }

            // A partir de aqui sabemos que la devolucion esta pendiente.
            double multa = diasRetraso * TARIFA_DIARIA;
            boolean topada = multa >= MULTA_MAXIMA;
            if (topada) {
                multa = MULTA_MAXIMA;
            }

            String gravedad;
            if (diasRetraso == 0) {
                gravedad = "SIN RETRASO";
            } else if (diasRetraso <= UMBRAL_LEVE) {
                gravedad = "LEVE";
            } else {
                gravedad = "GRAVE";
            }

            revisadas++;
            totalPendiente += multa;

            System.out.printf("  #%d  %3d dias  %-11s  %6.2f EUR%s%n",
                              n, diasRetraso, gravedad, multa,
                              topada ? "  <-- TOPE" : "");

            // --- BREAK: parar en cuanto se cumple la condicion de escalado ---
            if (topada) {
                escalado = true;
                devolucionEscalada = n;
                System.out.println("      Multa maxima alcanzada: se detiene la revision.");
                break;                 // abandona el bucle por completo
            }
        }

        System.out.println();
        System.out.println("=== RESUMEN ===");
        System.out.printf("%-28s %d de %d%n", "Devoluciones revisadas:", revisadas,
                          TOTAL_PENDIENTES);
        System.out.printf("%-28s %d%n",       "Omitidas por estar pagadas:", omitidasPorPago);
        System.out.printf("%-28s %.2f EUR%n", "Importe pendiente revisado:", totalPendiente);

        if (escalado) {
            System.out.printf("%-28s si (devolucion #%d)%n", "Escalado a RRHH:",
                              devolucionEscalada);
            System.out.println("AVISO: quedan devoluciones sin revisar tras la parada.");
        } else {
            System.out.printf("%-28s no%n", "Escalado a RRHH:");
        }
    }
}

Salida:

=== REVISION DE DEVOLUCIONES PENDIENTES ===
Empresa: Nexus Software

  #1   37 dias  GRAVE          9,25 EUR
  #2 ... ya pagada, se omite
  #3  111 dias  GRAVE         20,00 EUR  <-- TOPE
      Multa maxima alcanzada: se detiene la revision.

=== RESUMEN ===
Devoluciones revisadas:      2 de 8
Omitidas por estar pagadas:  1
Importe pendiente revisado:  29,25 EUR
Escalado a RRHH:             si (devolucion #3)
AVISO: quedan devoluciones sin revisar tras la parada.

Analiza lo que ha pasado, porque condensa toda la lección:

  • La devolución #2 activó el continue: se contó como omitida y no entró en los acumuladores. El bucle siguió.
  • La devolución #3 activó el break: se contabilizó primero (por eso totalPendiente la incluye) y después se abandonó el bucle. Las devoluciones #4 a #8 no se llegaron a mirar.
  • El orden dentro del cuerpo importa: si el break estuviera antes de totalPendiente += multa, la multa topada no se habría sumado. Coloca siempre las salidas donde tenga sentido según lo que debas contabilizar.
  • Y una vez más, la limitación de fondo: al acabar solo quedan cifras agregadas. No hay forma de listar cuáles fueron las devoluciones revisadas, ni de retomar la revisión donde se quedó. Eso exige guardar los datos, y para eso hacen falta las colecciones del módulo 5.

Errores Comunes y Consejos

1. Creer que break en un switch sale del bucle. Es la trampa del apartado 4. Si tu menú "no se cierra nunca", es esto.

2. continue en un while sin haber actualizado antes. Bucle infinito. Si usas continue, prefiere for.

3. Código inalcanzable tras break o continue.

if (multa >= MULTA_MAXIMA) {
    break;
    System.out.println("Nunca se imprime");   // ERROR: unreachable statement
}

Java lo detecta en compilación, lo cual es una ayuda.

4. break en el sitio equivocado dentro del cuerpo. Salir antes de acumular o después de imprimir cambia el resultado. Decide conscientemente: ¿la iteración que dispara la salida cuenta o no cuenta?

5. Etiquetas mal colocadas. La etiqueta debe ir inmediatamente antes del bucle al que se refiere. Si hay una sentencia en medio, no compila.

6. Abusar de las etiquetas. Más de una en el mismo método suele indicar que falta descomponer en métodos. Es legal, no es bueno.

7. Confundir return con break en main. return mata el programa entero y se salta el resumen final y el cierre del Scanner.

8. Consejo: un solo punto de salida por bucle, si puedes. Un bucle con cuatro break repartidos es difícil de razonar. Si te ocurre, la condición de parada probablemente merece ser una variable booleana con nombre.

9. Consejo: continue al principio del cuerpo, break donde corresponda. El patrón idiomático es agrupar todos los continue de filtrado en las primeras líneas del cuerpo, como cláusulas de guarda, y dejar el trabajo real plano debajo.

10. Consejo: comenta la razón, no la mecánica. break; // sale del bucle no aporta nada. break; // ya hemos encontrado el primero, no hace falta seguir sí.

Ejercicios

Ejercicio 1: primer día que supera un presupuesto

Nexus Software asigna un presupuesto de 50,00 € para multas por empleado y trimestre. Escribe un programa que recorra los días de retraso de 1 a 500 acumulando la multa sin tope (dias * TARIFA_DIARIA, sumada día a día) y que se detenga con break en cuanto el acumulado supere el presupuesto. Debe imprimir en qué día ocurrió y cuál era el acumulado exacto en ese momento. Añade también un contador de iteraciones efectivamente ejecutadas para comprobar que el break ha ahorrado trabajo.

Ejercicio 2: filtrar con continue y detectar el fallo del while

Escribe dos versiones del mismo programa, que recorre los números de referencia de préstamo del 1 al 20 e imprime únicamente los que no son múltiplos de 3 (los múltiplos de 3 corresponden a préstamos ya archivados y se descartan con continue):

  1. Con un bucle for.
  2. Con un bucle while.

En la versión while, coloca deliberadamente el incremento después del continue para reproducir el bucle infinito, obsérvalo (interrúmpelo con Ctrl + C) y después corrígelo. Explica por escrito por qué el for no sufre ese problema.

Ejercicio 3: menú con switch clásico y salida correcta

Escribe un menú en bucle con estas opciones, usando switch clásico (con : y break), no de flechas:

1. Revisar devoluciones pendientes
2. Consultar tope de multa
0. Salir

El reto es que el programa termine de verdad al elegir 0. Impleméntalo de dos formas distintas y compáralas:

  • Versión A: con una bandera booleana en la condición del while.
  • Versión B: con una etiqueta y break etiqueta.

Añade un contador de operaciones realizadas que se muestre al salir, y comprueba que en ambas versiones se imprime (pista: si usas return en lugar de break, no se imprimirá).

Soluciones

Solución 1

package com.nexussoftware.bibliotech;

public class BiblioTechApp {
    public static void main(String[] args) {

        final double TARIFA_DIARIA = 0.25;
        final double PRESUPUESTO = 50.0;
        final int DIAS_MAXIMOS = 500;

        double acumulado = 0.0;
        int diaDeCorte = 0;
        int iteraciones = 0;

        for (int dias = 1; dias <= DIAS_MAXIMOS; dias++) {

            iteraciones++;
            acumulado += dias * TARIFA_DIARIA;

            if (acumulado > PRESUPUESTO) {
                diaDeCorte = dias;
                break;              // ya tenemos la respuesta: no seguimos sumando
            }
        }

        System.out.println("=== CONTROL DE PRESUPUESTO DE MULTAS ===");
        System.out.printf("%-28s %.2f EUR%n", "Presupuesto:", PRESUPUESTO);

        if (diaDeCorte > 0) {
            System.out.printf("%-28s dia %d%n",     "Se supera en:", diaDeCorte);
            System.out.printf("%-28s %.2f EUR%n",   "Acumulado en ese punto:", acumulado);
        } else {
            System.out.printf("%-28s nunca en %d dias%n", "Se supera en:", DIAS_MAXIMOS);
        }

        System.out.printf("%-28s %d de %d%n", "Iteraciones ejecutadas:",
                          iteraciones, DIAS_MAXIMOS);
        System.out.printf("%-28s %d%n", "Iteraciones ahorradas:",
                          DIAS_MAXIMOS - iteraciones);
    }
}

Salida:

=== CONTROL DE PRESUPUESTO DE MULTAS ===
Presupuesto:                 50,00 EUR
Se supera en:                dia 20
Acumulado en ese punto:      52,50 EUR
Iteraciones ejecutadas:      20 de 500
Iteraciones ahorradas:       480

Comprobación manual: la suma de 1 + 2 + … + 20 es 210; multiplicada por 0,25 da 52,50 €, que efectivamente supera los 50 €. Con 19 días serían 190 × 0,25 = 47,50 €, aún por debajo. El break ha ahorrado el 96 % del trabajo, y ese ahorro es el argumento principal para usarlo en búsquedas.

Solución 2

Versión con for:

package com.nexussoftware.bibliotech;

public class BiblioTechApp {
    public static void main(String[] args) {

        int mostrados = 0;
        int archivados = 0;

        System.out.println("Referencias de prestamo activas (1-20):");

        for (int referencia = 1; referencia <= 20; referencia++) {

            if (referencia % 3 == 0) {
                archivados++;
                continue;     // en un for, salta a referencia++ y luego a la condicion
            }

            System.out.printf("  REF-%03d%n", referencia);
            mostrados++;
        }

        System.out.printf("Mostradas %d, archivadas %d.%n", mostrados, archivados);
    }
}

Salida (abreviada): REF-001, REF-002, REF-004, REF-005, REF-007… y al final Mostradas 14, archivadas 6.

Versión con while, primero rota:

int referencia = 1;
while (referencia <= 20) {
    if (referencia % 3 == 0) {
        continue;                  // BUG: salta el incremento de abajo
    }
    System.out.printf("  REF-%03d%n", referencia);
    referencia++;
}

Al ejecutarla, la salida se detiene tras REF-002 y el programa se queda colgado sin imprimir nada: referencia vale 3, la condición del if se cumple siempre y nunca se llega al referencia++. Interrúmpelo con Ctrl + C.

Versión con while corregida: hay que incrementar antes de saltar, lo que obliga a trabajar con una copia del valor:

int referencia = 0;
int mostrados = 0;
int archivados = 0;

while (referencia < 20) {

    referencia++;                  // el incremento va PRIMERO, siempre se ejecuta

    if (referencia % 3 == 0) {
        archivados++;
        continue;                  // ahora es seguro
    }

    System.out.printf("  REF-%03d%n", referencia);
    mostrados++;
}
System.out.printf("Mostradas %d, archivadas %d.%n", mostrados, archivados);

Por qué el for no sufre el problema: en un for, la actualización (referencia++) forma parte de la cabecera, no del cuerpo. continue salta al final del cuerpo, pero la actualización se ejecuta después de eso, siempre, sea cual sea la forma en que la iteración haya terminado. En un while, la actualización es una línea más del cuerpo y continue puede saltársela. Esta es la razón práctica de la regla "si vas a usar continue, usa for".

Solución 3

Versión A: bandera booleana.

package com.nexussoftware.bibliotech;

import java.util.Scanner;

public class BiblioTechApp {
    public static void main(String[] args) {

        final double TARIFA_DIARIA = 0.25;
        final double MULTA_MAXIMA = 20.0;

        Scanner scanner = new Scanner(System.in);
        boolean salir = false;
        int operaciones = 0;

        while (!salir) {

            System.out.println();
            System.out.println("=== BIBLIOTECH (version A) ===");
            System.out.println("  1. Revisar devoluciones pendientes");
            System.out.println("  2. Consultar tope de multa");
            System.out.println("  0. Salir");
            System.out.print("Opcion: ");

            String opcion = scanner.nextLine().trim();

            switch (opcion) {
                case "1":
                    System.out.println("  3 devoluciones pendientes de revision.");
                    operaciones++;
                    break;                 // sale del switch: correcto y suficiente

                case "2":
                    int diasTope = (int) Math.ceil(MULTA_MAXIMA / TARIFA_DIARIA);
                    System.out.printf("  El tope de %.2f EUR se alcanza a los %d dias.%n",
                                      MULTA_MAXIMA, diasTope);
                    operaciones++;
                    break;

                case "0":
                    salir = true;          // la CONDICION del while cierra el bucle
                    break;

                default:
                    System.out.println("  Opcion no reconocida.");
            }
        }

        System.out.printf("Sesion finalizada. Operaciones realizadas: %d%n", operaciones);
        scanner.close();
    }
}

Versión B: etiqueta.

package com.nexussoftware.bibliotech;

import java.util.Scanner;

public class BiblioTechApp {
    public static void main(String[] args) {

        final double TARIFA_DIARIA = 0.25;
        final double MULTA_MAXIMA = 20.0;

        Scanner scanner = new Scanner(System.in);
        int operaciones = 0;

        bucleMenu:                        // etiqueta, justo antes del bucle
        while (true) {

            System.out.println();
            System.out.println("=== BIBLIOTECH (version B) ===");
            System.out.println("  1. Revisar devoluciones pendientes");
            System.out.println("  2. Consultar tope de multa");
            System.out.println("  0. Salir");
            System.out.print("Opcion: ");

            String opcion = scanner.nextLine().trim();

            switch (opcion) {
                case "1":
                    System.out.println("  3 devoluciones pendientes de revision.");
                    operaciones++;
                    break;                // sale solo del switch

                case "2":
                    int diasTope = (int) Math.ceil(MULTA_MAXIMA / TARIFA_DIARIA);
                    System.out.printf("  El tope de %.2f EUR se alcanza a los %d dias.%n",
                                      MULTA_MAXIMA, diasTope);
                    operaciones++;
                    break;

                case "0":
                    break bucleMenu;      // rompe el BUCLE etiquetado

                default:
                    System.out.println("  Opcion no reconocida.");
            }
        }

        System.out.printf("Sesion finalizada. Operaciones realizadas: %d%n", operaciones);
        scanner.close();
    }
}

Sesión idéntica en ambas versiones:

Opcion: 2
  El tope de 20,00 EUR se alcanza a los 80 dias.
Opcion: 5
  Opcion no reconocida.
Opcion: 1
  3 devoluciones pendientes de revision.
Opcion: 0
Sesion finalizada. Operaciones realizadas: 2

Comparación. La versión A no necesita etiquetas y su condición de salida es visible en la cabecera (while (!salir)), lo que la hace más fácil de leer para alguien que llega nuevo al código; a cambio, la variable salir es un elemento extra que hay que mantener. La versión B es más directa —un solo salto, sin variable auxiliar— pero exige que el lector conozca las etiquetas y, si el método creciera con más bucles, empezaría a resultar confusa. Para un menú, el curso recomienda la versión A, y es la que se usará en el proyecto de la lección 02-06.

Y comprueba el detalle de la pista: si en el case "0" de cualquiera de las dos versiones escribieras return; en lugar de break bucleMenu; o salir = true;, el programa terminaría al instante y la línea Sesion finalizada. Operaciones realizadas: 2 nunca se imprimiría, además de dejar el Scanner sin cerrar. Es exactamente la diferencia entre "salir del bucle" y "terminar el programa".

Conclusión

Ya controlas el flujo dentro de un bucle. break abandona el bucle en cuanto tienes la respuesta y convierte una búsqueda exhaustiva en una búsqueda eficiente; continue descarta las iteraciones que no interesan y mantiene el cuerpo plano, como una cláusula de guarda. Conoces la trampa más famosa del lenguaje —el break que sale del switch y no del bucle— y sus tres soluciones: bandera, switch con flechas y etiqueta. Sabes usar break etiqueta y continue etiqueta para gobernar bucles anidados, y también por qué conviene desconfiar de ellas y preferir banderas o, en cuanto sepas escribirlos, métodos con return. Y tienes clara la diferencia entre salir de un bucle y terminar el programa.

BiblioTechApp puede ahora recorrer la cola de devoluciones pendientes, saltarse las ya pagadas, detenerse en la primera con multa máxima y escalar el caso, todo con acumuladores escalares y un único bucle. El programa hace bastantes cosas, y ahí aparece el problema siguiente: cuando el resultado no es el esperado, ya no basta con releer el código. Hay demasiadas variables cambiando en demasiadas iteraciones.

La siguiente lección, Depuración y Trazas de Ejecución, te da las herramientas para ver qué ocurre realmente dentro de tu programa: trazas con System.out.println bien colocadas, el depurador del IDE con puntos de interrupción (incluidos los condicionales, para detenerse solo cuando diasRetraso > 30), la ventana de variables, la evaluación de expresiones, cómo leer una traza de pila y, sobre todo, un método sistemático para localizar un bug en lugar de buscarlo a ciegas.

Curso de Programación en Java

Módulo 1: Introducción a Java

Módulo 2: Flujo de Control

Módulo 3: Programación Orientada a Objetos

Módulo 4: Programación Orientada a Objetos Avanzada

Módulo 5: Estructuras de Datos y Colecciones

Módulo 6: Manejo de Excepciones

Módulo 7: Entrada/Salida de Archivos

Módulo 8: Multihilo y Concurrencia

Módulo 9: Redes

Módulo 10: Temas Avanzados

Módulo 11: Frameworks y Librerías de Java

Módulo 12: Construcción de Aplicaciones del Mundo Real

© Copyright 2026. Todos los derechos reservados