Saltar a contenido

4. Iteración: while y do-while

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: while y do-while)
CE 3.5 (crea programas ejecutables combinando selección e iteración)
CE 3.6 (prueba y depura programas, incluida la detección de bucles infinitos)
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 26 h de la UD3).

4.1 ¿De qué va esta sección? — De decidir una vez a repetir hasta que toque parar

Hasta ahora tu programa toma una decisión y sigue adelante: entra en un if, ejecuta un bloque, y continúa. Ni una sola vez has conseguido que Java haga la misma cosa diez veces, cien veces, o hasta que tú se lo digas, sin copiar y pegar la misma línea una y otra vez. Eso termina hoy.

Esta sección presenta la iteración: la capacidad de repetir un bloque de código mientras se cumpla una condición. Vas a aprender las dos estructuras más antiguas y fundamentales para hacerlo en Java: while y do-while. Son primas hermanas del if que ya dominas —ambas usan una condición booleana entre paréntesis— pero en vez de "decidir una vez", "deciden repetidamente".

Vamos a cubrir, en este orden:

  1. Por qué hace falta la iteración: el problema de repetir código a mano.
  2. El bucle while: sintaxis, anatomía, y su propiedad más importante —puede no ejecutarse nunca—.
  3. El bucle do-while: sintaxis, anatomía, y su propiedad más importante —se ejecuta siempre al menos una vez—.
  4. La comparativa entre ambos y el criterio para elegir uno u otro.
  5. Los bucles infinitos: el peligro del accidental y la utilidad del intencionado con while (true) + break.
  6. La validación robusta de entrada con Scanner: por fin vamos a poder volver a preguntar cuando el usuario se equivoca, en vez de simplemente rendirnos.
  7. Los menús interactivos, el caso de uso más natural de do-while.
  8. Un ejemplo guiado completo: el mini-juego "Adivina el número".
  9. Las trampas clásicas de todo bucle, empezando por el temido bucle infinito accidental.
  10. Varios ejemplos resueltos paso a paso.

Analogía: la atracción de feria y la heladería con degustación

Piensa en el while como la atracción de feria con altura mínima: el operario mide tu altura antes de dejarte subir. Si no llegas, no subes ni una sola vez. Puede que la atracción funcione toda la tarde sin que tú te montes nunca.

Piensa en el do-while como una heladería que reparte una cucharada de degustación a cada persona que entra por la puerta, y solo después te pregunta si quieres un cucurucho completo. Todo el mundo que entra prueba la cucharada al menos una vez; la pregunta ("¿quieres más?") llega después, no antes.

Esa diferencia —comprobar antes de actuar frente a comprobar después de actuar— es la única diferencia real entre while y do-while. Todo lo demás en esta sección son consecuencias de esa idea.

Abre IntelliJ, crea una clase PruebasBucles en tu proyecto Maven, y vamos a ello.


4.2 ¿Por qué necesitamos bucles? El problema de repetir código

Imagina que quieres imprimir los números del 1 al 5. Con lo que sabes hasta ahora, tendrías que escribir esto:

1
2
3
4
5
6
// Sin bucles: repetición a mano, línea por línea
IO.println(1);
IO.println(2);
IO.println(3);
IO.println(4);
IO.println(5);

Para 5 números, es tedioso pero soportable. ¿Y para 1000 números? ¿Y si no sabes de antemano cuántas veces hay que repetir algo, porque depende de lo que escriba el usuario? Copiar y pegar código no es programar: es transcribir. Un programa de verdad necesita poder decir "repite esto mientras se cumpla tal condición", sin que el número de repeticiones esté escrito a fuego en el código fuente.

Esa es exactamente la motivación de la iteración: sustituir la repetición manual por una condición de continuación que Java evalúa una y otra vez, decidiendo en cada vuelta si sigue o para.

flowchart TD
    Inicio([Inicio]) --> Condicion{"¿Se cumple\nla condición?"}
    Condicion -->|Sí| Bloque["Ejecuta el bloque\nde código"]
    Bloque --> Condicion
    Condicion -->|No| Fin([Fin])

Fíjate en la flecha que vuelve de "Bloque" hacia "Condicion": esa flecha es la que no existía en los diagramas de if de la sección 2. Ahí está toda la diferencia entre seleccionar y repetir.


4.3 El bucle while: la estructura de repetición con precondición

4.3.1 Sintaxis

1
2
3
4
while (condicion) {
    // Bloque que se repite mientras 'condicion' sea true
}
// Aquí el programa continúa, en cuanto 'condicion' sea false

La sintaxis es prácticamente idéntica a la del if: palabra clave, condición booleana entre paréntesis, bloque entre llaves. La diferencia está en el comportamiento: en un if, el bloque se ejecuta como mucho una vez; en un while, el bloque se ejecuta tantas veces como la condición siga siendo true, y Java vuelve a comprobar la condición después de cada ejecución del bloque.

4.3.2 Anatomía: la condición de continuación, no de parada

Aquí hay una confusión muy habitual al empezar: mucha gente lee while (contador <= 5) como "para cuando contador llegue a 5". Eso está mal formulado. La lectura correcta es:

"Sigue repitiendo mientras contador sea menor o igual que 5. En el instante en que deje de serlo, para."

La condición de un while es una condición de continuación (sigo mientras esto es verdad), no una condición de parada (paro cuando esto es verdad). Son formas equivalentes de pensar el mismo problema, pero la de "continuación" es la que coincide exactamente con lo que Java hace: comprobar la condición y, si es true, ejecutar el bloque; si es false, saltárselo.

4.3.3 Primer ejemplo: contar del 1 al 5

1
2
3
4
5
6
7
8
// Programa: cuenta del 1 al 5 usando un bucle while
int contador = 1;

while (contador <= 5) {
    IO.println("Contador: " + contador);
    contador++;
}
IO.println("Bucle terminado.");

Salida:

1
2
3
4
5
6
Contador: 1
Contador: 2
Contador: 3
Contador: 4
Contador: 5
Bucle terminado.

Fíjate en tres piezas imprescindibles, que siempre van juntas en un while bien construido:

  1. Inicialización: int contador = 1; — se declara antes del bucle, fuera de él.
  2. Condición: contador <= 5 — se comprueba antes de cada vuelta.
  3. Actualización: contador++; — se ejecuta dentro del bloque, normalmente al final.

Si falta cualquiera de las tres, el bucle no hace lo que esperas. Si falta la actualización, verás en la sección 4.6 que el resultado es un desastre.

4.3.4 Trazado paso a paso: qué pasa por dentro

Trazar un bucle a mano —seguir el valor de las variables vuelta a vuelta, en una tabla— es la técnica más eficaz para entender (y depurar) cualquier iteración. Vamos a trazar el ejemplo anterior:

Iteración contador al empezar ¿contador <= 5? Acción realizada contador al terminar la vuelta
1 1 true Imprime "Contador: 1" 2
2 2 true Imprime "Contador: 2" 3
3 3 true Imprime "Contador: 3" 4
4 4 true Imprime "Contador: 4" 5
5 5 true Imprime "Contador: 5" 6
6 6 false No entra al bloque. Sale del while.

Fíjate en la última fila: la condición se comprueba una vez más de lo que el bloque se ejecuta. Es la comprobación número 6 la que finalmente da false y saca al programa del bucle. Esta tabla es tu mejor herramienta cuando un bucle "hace una cosa rara": trázalo a mano, columna a columna, y el error salta a la vista casi siempre.

Practica el trazado antes de ejecutar

Antes de darle a "Run" en IntelliJ, intenta predecir en papel (o en un comentario) cuántas veces se va a ejecutar tu bucle y qué va a imprimir. Si tu predicción no coincide con la salida real, has encontrado un error de lógica —y lo has encontrado tú, sin depurador, solo con papel y lápiz—.

4.3.5 El caso especial: el bucle que nunca se ejecuta

Aquí está la propiedad que más diferencia al while de todo lo que has visto hasta ahora: si la condición es false desde el principio, el bloque no se ejecuta ni una sola vez.

1
2
3
4
5
6
7
8
// Programa: intenta contar, pero la condición ya es falsa desde el inicio
int contador = 10;

while (contador <= 5) {
    IO.println("Esto nunca se imprime.");
    contador++;
}
IO.println("El bucle no se ejecutó ni una sola vez.");

Salida:

El bucle no se ejecutó ni una sola vez.

Esto no es un error: es exactamente lo que se espera de un while. La condición se comprueba antes de entrar, así que si nace siendo false, el bloque se queda sin ejecutar. Esta propiedad —"precondición"— es la que hace del while la estructura adecuada quando no sabes de antemano si hará falta ejecutar el bloque ni una sola vez. Piensa, por ejemplo, en procesar los elementos de una lista que podría llegar vacía (lo verás formalmente con colecciones en UD6): si está vacía, el bucle no debe ejecutarse ninguna vez, y el while lo garantiza de forma natural.

4.3.6 Diagrama de flujo del while

flowchart TD
    Inicio([Inicio]) --> Init["contador = 1"]
    Init --> Condicion{"contador <= 5 ?"}
    Condicion -->|Sí| Bloque["Imprime contador\ncontador++"]
    Bloque --> Condicion
    Condicion -->|No| Fin([Fin])

Compáralo con el diagrama de la sección 4.2: es el mismo esqueleto, con la condición concreta y la actualización de la variable de control ya dentro del bloque.


4.4 El bucle do-while: la estructura de repetición con poscondición

4.4.1 Sintaxis (y el punto y coma que se te va a olvidar)

1
2
3
do {
    // Bloque que se ejecuta al menos una vez
} while (condicion);

Fíjate en el orden: primero do y el bloque, después while (condicion). Y fíjate también en un detalle que te va a morder en algún momento: el do-while termina con un punto y coma obligatorio después del paréntesis de la condición. Ningún otro bloque que hayas visto hasta ahora —ni if, ni while— lleva ese ; final. El do-while sí, porque en realidad toda la estructura do { ... } while (condicion); es, gramaticalmente, una única sentencia.

1
2
3
4
5
6
7
8
9
// BIEN: el ; final es obligatorio
do {
    IO.println("Hola");
} while (false);

// MAL: no compila, falta el ; final
do {
    IO.println("Hola");
} while (false)

IntelliJ te lo marcará como error de compilación si lo olvidas ("';' expected"), así que no es un bug silencioso como algunos que verás más adelante: el compilador te avisa. Aun así, acostúmbrate desde ya a escribirlo bien.

4.4.2 Ejecuta siempre, al menos una vez

Esta es la propiedad estrella del do-while, la que lo distingue completamente del while: el bloque se ejecuta primero, y la condición se comprueba después. Eso significa que, pase lo que pase, el bloque se ejecuta como mínimo una vez, incluso si la condición es false desde el principio.

1
2
3
4
5
6
7
8
9
// Programa: el bloque se ejecuta una vez, aunque la condición sea falsa desde el inicio
int contador = 10;

do {
    IO.println("Esto SÍ se imprime, una vez.");
    contador++;
} while (contador <= 5);

IO.println("El bucle se ejecutó exactamente una vez.");

Salida:

Esto SÍ se imprime, una vez.
El bucle se ejecutó exactamente una vez.

Compara esto con el ejemplo equivalente de la sección 4.3.5: con while, el bloque no se ejecutó ninguna vez; con do-while, se ejecuta una vez sí o sí, aunque la condición final resulte false. Esa diferencia de "cero veces" contra "al menos una vez" es la que vas a usar como criterio de elección en la sección 4.5.

4.4.3 Primer ejemplo: pedir un número positivo

// Programa: pide un número positivo, repitiendo la pregunta si hace falta
Scanner sc = new Scanner(System.in);
int numero;

do {
    IO.print("Introduce un número positivo: ");
    numero = Integer.parseInt(sc.nextLine().trim());
} while (numero <= 0);

IO.println("Gracias, has introducido: " + numero);

Fíjate en algo importante: la variable numero se declara antes del do, sin inicializar con un valor "válido" a propósito, porque su primer valor real llega dentro del bloque, en la primera vuelta. Esto es distinto de lo que verás en la sección 4.7 para while, donde sí hace falta un truco de inicialización.

4.4.4 Diagrama de flujo del do-while

flowchart TD
    Inicio([Inicio]) --> Bloque["Pide 'numero' por teclado"]
    Bloque --> Condicion{"numero <= 0 ?"}
    Condicion -->|Sí| Bloque
    Condicion -->|No| Fin([Fin])

Fíjate en la posición del rombo: está después del bloque, no antes. Ese es el único cambio estructural respecto al diagrama del while, y de él se derivan todas las diferencias de comportamiento que vas a ver a continuación.


4.5 while frente a do-while: comparativa y criterio de elección

4.5.1 Comparativa visual

flowchart LR
    A["while\ncomprueba ANTES\nde entrar al bloque"] --> B["Puede ejecutarse\n0 veces"]
    C["do-while\ncomprueba DESPUÉS\nde ejecutar el bloque"] --> D["Se ejecuta SIEMPRE\nal menos 1 vez"]

    style A fill:#e3f2fd,stroke:#1565c0
    style B fill:#e3f2fd,stroke:#1565c0
    style C fill:#fff3e0,stroke:#e65100
    style D fill:#fff3e0,stroke:#e65100

4.5.2 Tabla comparativa

Característica while do-while
¿Cuándo se evalúa la condición? Antes de cada vuelta (precondición). Después de cada vuelta (poscondición).
¿Puede ejecutarse 0 veces? Sí, si la condición ya es false al llegar. No, se ejecuta como mínimo 1 vez.
¿Necesita ; al final? No. , obligatorio tras while (condicion).
¿Dónde se sitúa el rombo en el diagrama? Antes del bloque. Después del bloque.
Caso de uso típico Cuando el bloque podría no ser necesario nunca (ej. procesar algo que podría no existir). Cuando el bloque siempre hace falta al menos una vez (ej. mostrar un menú, pedir un dato).
Ejemplo mental "Mientras queden monedas en la máquina, sigue expendiendo." "Reparte una muestra gratis a todo el que entra, y luego pregunta si quiere más."

4.5.3 Transformar un while en un do-while (y viceversa)

Cualquier while se puede reescribir como un do-while envuelto en un if que comprueba la condición una vez antes de empezar (y viceversa, un do-while es un while con una "primera vuelta gratis"). No vas a necesitar hacer esta transformación en el día a día, pero entenderla te ayuda a fijar el concepto:

// Un while...
while (condicion) {
    accion();
}

// ...equivale a comprobar una vez antes, y luego un do-while:
if (condicion) {
    do {
        accion();
    } while (condicion);
}

Si la primera comprobación (if) es false, el do-while interior no llega a ejecutarse ni una vez, exactamente igual que el while original. Esta equivalencia demuestra que la diferencia entre ambos no es "de fondo" (las dos estructuras pueden simular el comportamiento de la otra), sino de conveniencia: elegir la que exprese mejor tu intención con menos código.

4.5.4 Regla del profesor para elegir

Hazte esta pregunta: ¿esta acción tiene que ocurrir siempre, al menos una vez, pase lo que pase?

  • Si la respuesta es (pedir un dato, mostrar un menú, dar la bienvenida) → do-while.
  • Si la respuesta es depende (podría no hacer falta nunca, según el estado inicial) → while.

No es una regla arbitraria: es un reflejo directo de lo que cada estructura garantiza por diseño. Elegir mal no produce un error de compilación —ambas compilan igual de bien—, pero sí puede producir un error de lógica sutil: un while que debería haberse ejecutado una vez y no lo hace, o un do-while que ejecuta una acción no deseada antes de comprobar si debía hacerlo.


4.6 Bucles infinitos: el peligro y la herramienta

4.6.1 El bucle infinito accidental

Un bucle infinito ocurre cuando la condición de continuación nunca llega a ser false. El error más común, con diferencia, es olvidar actualizar la variable de control dentro del bloque:

1
2
3
4
5
6
// BUG: bucle infinito, 'contador' nunca cambia
int contador = 1;
while (contador <= 5) {
    IO.println("Esto se repite para siempre...");
    // Falta: contador++;
}

Si ejecutas este código, tu programa no va a terminar nunca por sí solo: va a imprimir la misma línea sin parar, consumiendo CPU, hasta que tú intervengas manualmente. Vale la pena que lo pruebes una vez a propósito en IntelliJ —de forma controlada— para reconocer la sensación de "el programa no responde" y saber qué hacer.

Cómo se ve un bucle infinito en marcha

En la consola de IntelliJ verás cómo el texto se imprime sin parar, cada vez más rápido, llenando la ventana. Si tu programa "no hace nada" durante mucho más tiempo del esperado y no imprime nada, o si imprime lo mismo sin parar, sospecha inmediatamente de un bucle infinito.

4.6.2 Cómo detener un programa que no termina

Adelanto: la parada de emergencia (formal en la sección 10)

La sección 10 de esta unidad está dedicada por completo a la depuración con IntelliJ IDEA: breakpoints, step over, step into, y mucho más. Por ahora solo necesitas saber cómo parar un programa que se ha quedado colgado en un bucle infinito:

  • En la ventana Run (la consola donde ves la salida de tu programa), hay un cuadrado rojo en la barra lateral izquierda. Haz clic ahí para forzar la parada del programa.
  • El atajo de teclado es Ctrl+F2 (Windows/Linux) o Cmd+F2 (Mac).

No hay nada de qué avergonzarse: absolutamente todo programador ha provocado un bucle infinito por accidente, muchas veces. Lo importante es reconocerlo rápido y saber pararlo sin tener que cerrar el IDE entero.

4.6.3 El bucle infinito intencionado: while (true) + break

Hasta ahora hemos hablado del bucle infinito como un bug. Pero existe una versión completamente legítima y muy usada en la industria: el bucle infinito intencionado, escrito literalmente como while (true), combinado con una sentencia que lo interrumpe desde dentro cuando corresponde: break.

Ya conoces break de la sección 3: lo viste en el switch clásico, donde break corta la ejecución para evitar el fall-through de un case al siguiente. Aquí aparece su segunda aplicación, con el mismo significado exacto: break interrumpe inmediatamente la estructura que lo contiene —antes era el switch, ahora es el bucle— y el programa continúa justo después de ella.

// Bucle infinito intencionado: sigue pidiendo datos hasta que el usuario escriba "fin"
Scanner sc = new Scanner(System.in);

while (true) {
    IO.print("Escribe un número (o 'fin' para terminar): ");
    String entrada = sc.nextLine().trim();

    if (entrada.equalsIgnoreCase("fin")) {
        break;   // sale del while inmediatamente, sin comprobar ninguna condición más
    }

    int numero = Integer.parseInt(entrada);
    IO.println("El doble de " + numero + " es " + (numero * 2));
}

IO.println("Programa terminado.");

break en profundidad: sección 7

El tratamiento completo de break —incluido el labeled break para salir de bucles anidados desde dentro— llega en la sección 7, junto con continue y el uso de return dentro de un bucle. Por ahora te basta con lo que ya sabías del switch: break sale inmediatamente de la estructura que lo envuelve.

4.6.4 El patrón "loop-and-a-half"

El ejemplo anterior tiene un nombre en la industria: loop-and-a-half (bucle y medio). Ocurre cuando la decisión de seguir o parar no puede tomarse limpiamente ni al principio ni al final del bucle, porque depende de algo que solo puedes averiguar a mitad del bloque (en este caso, leer la entrada del usuario y comprobar si es "fin").

Con un while normal tendrías que leer el dato dos veces: una vez antes del bucle (para tener algo que comprobar en la condición) y otra vez al final de cada vuelta (para la siguiente comprobación), duplicando código:

// Sin while(true)+break: hay que leer el dato DOS veces (antes del bucle y al final)
Scanner sc = new Scanner(System.in);

IO.print("Escribe un número (o 'fin' para terminar): ");
String entrada = sc.nextLine().trim();

while (!entrada.equalsIgnoreCase("fin")) {
    int numero = Integer.parseInt(entrada);
    IO.println("El doble de " + numero + " es " + (numero * 2));

    IO.print("Escribe un número (o 'fin' para terminar): ");
    entrada = sc.nextLine().trim();     // la misma lectura, repetida
}
IO.println("Programa terminado.");

Funciona, pero fíjate en la duplicación: la llamada sc.nextLine() y su mensaje aparecen dos veces en el código, una antes del bucle y otra al final. El patrón while (true) + break evita esa duplicación leyendo el dato una sola vez, dentro del bucle, y decidiendo con un if si toca salir. Es la razón por la que este patrón, lejos de ser una chapuza, es una técnica reconocida y recomendada quando encaja este tipo de situación.

Situación Recomendación
La condición de parada se puede comprobar de forma natural al principio o al final del bucle. while o do-while normal.
La condición de parada depende de un dato que solo se conoce a mitad del bloque. while (true) + break (loop-and-a-half).
Necesitas varias condiciones de salida distintas en puntos diferentes del bloque. while (true) con varios break condicionales (con moderación).

4.7 Validación robusta de entrada con Scanner y while

4.7.1 El cierre del cliff-hanger de la sección 2

¿Recuerdas la sección 2.7.7 ("Lo que NO podemos hacer todavía")? Allí validábamos la entrada del usuario con String.matches(...), pero cuando el dato no era válido, lo único que podíamos hacer era imprimir un error y terminar el programa con return. No teníamos forma de "volver a preguntar": sin bucles, cada línea de código se ejecuta una sola vez, así que no había manera de repetir la pregunta.

Eso cambia hoy. Con while puedes envolver la pregunta en una condición de continuación: "sigue preguntando mientras el dato siga sin ser válido". Es el mismo patrón de validación de siempre, pero ahora con la capacidad de insistir.

4.7.2 Patrón: pedir hasta que sea válido

// Patrón: repetir la pregunta hasta que la entrada sea numérica
Scanner sc = new Scanner(System.in);
String entrada = "";
boolean esValida = false;

while (!esValida) {
    IO.print("Escribe tu edad (solo dígitos): ");
    entrada = sc.nextLine().trim();
    esValida = entrada.matches("\\d+");

    if (!esValida) {
        IO.println("Error: solo se admiten dígitos. Inténtalo de nuevo.");
    }
}

int edad = Integer.parseInt(entrada);
IO.println("Edad registrada: " + edad);

Fíjate en el pequeño truco de inicialización: boolean esValida = false; se declara antes del bucle, con el valor que garantiza que el while entre al menos una vez a preguntar. Como el while comprueba la condición antes de actuar (precondición), necesitas inicializar la variable de control con un valor que "falle a propósito" la primera vez, para forzar la primera pregunta. Esta es la contrapartida del do-while, que no necesita este truco porque garantiza la primera ejecución por diseño.

El truco de inicialización del while

Cuando uses while para repetir una pregunta al usuario, inicializa la variable de control con un valor que deliberadamente incumpla la condición de salida, para asegurar que el bucle se ejecute al menos la primera vez. Es exactamente lo contrario de lo que haría un do-while, que no necesita este truco.

4.7.3 Patrón: pedir hasta que esté en rango

Combinando la validación de formato con la validación de rango (ya vista en la sección 2 con if), pero ahora en bucle:

// Patrón: repetir hasta que la edad sea numérica Y esté en el rango [0, 120]
Scanner sc = new Scanner(System.in);
int edad = -1;   // valor inicial que garantiza fallar la condición de rango

while (edad < 0 || edad > 120) {
    IO.print("Edad (0-120): ");
    String entrada = sc.nextLine().trim();

    if (entrada.matches("\\d+")) {
        edad = Integer.parseInt(entrada);
        if (edad < 0 || edad > 120) {
            IO.println("Error: la edad debe estar entre 0 y 120.");
        }
    } else {
        IO.println("Error: escribe solo dígitos.");
    }
}

IO.println("Edad registrada correctamente: " + edad);

Aquí edad = -1 es el valor "trampa" que garantiza que la condición edad < 0 || edad > 120 sea true en la primera comprobación, forzando la primera vuelta del bucle. Después, cada vuelta intenta mejorar el valor de edad; el bucle no termina hasta que consigue un valor realmente válido.

4.7.4 Patrón: acumular con centinela (valor de parada)

Un centinela es un valor especial que el usuario introduce para indicar "he terminado", distinto de los datos "normales" que se están procesando. Es un patrón clásico para acumular una cantidad indeterminada de valores:

// Patrón: sumar números hasta que el usuario escriba 0 (el centinela)
Scanner sc = new Scanner(System.in);
int suma = 0;
int numero;

IO.println("Introduce números para sumar. Escribe 0 para terminar.");
do {
    IO.print("Número: ");
    numero = Integer.parseInt(sc.nextLine().trim());
    suma += numero;
} while (numero != 0);

IO.println("La suma total es: " + suma);

Fíjate en un detalle sutil: el 0 que actúa como centinela también se suma (suma += numero se ejecuta antes de comprobar la condición), pero como sumar 0 no cambia el resultado, no supone ningún problema en este caso. Si el centinela fuera, por ejemplo, -1 en un contexto donde -1 no es un valor de suma válido, tendrías que reorganizar el código para no sumarlo (por ejemplo, comprobando antes de sumar). Elegir bien el valor centinela —uno que nunca vaya a ser un dato real— es parte del diseño del algoritmo.


4.8 Menús interactivos con do-while

4.8.1 El patrón del menú repetido

Un menú de opciones es el ejemplo de manual del do-while: el menú tiene que mostrarse al menos una vez, siempre, sin excepción. Solo después de que el usuario elige una opción tiene sentido preguntar "¿seguimos o salimos?". Esa secuencia —mostrar, actuar, preguntar si continuar— encaja exactamente con la poscondición del do-while.

4.8.2 Ejemplo completo: menú con opción de salir

// Programa: menú repetido hasta que el usuario elige "Salir"
Scanner sc = new Scanner(System.in);
String opcion;

do {
    IO.println("--- Menú ---");
    IO.println("1. Saludar");
    IO.println("2. Despedir");
    IO.println("3. Salir");
    IO.print("Elige una opción: ");
    opcion = sc.nextLine().trim();

    if (opcion.equals("1")) {
        IO.println("¡Hola!");
    } else if (opcion.equals("2")) {
        IO.println("¡Adiós!");
    } else if (!opcion.equals("3")) {
        IO.println("Opción no reconocida.");
    }
} while (!opcion.equals("3"));

IO.println("Programa terminado.");

Observa la condición del do-while: !opcion.equals("3"), es decir, "sigue mientras la opción NO sea 3". Cuando el usuario finalmente escribe "3", el if (opcion.equals("1")) ni siquiera entra en ninguna de sus dos primeras ramas, y como la tercera rama es else if (!opcion.equals("3")), tampoco se ejecuta (porque opcion sí es "3"): no se imprime ningún mensaje intermedio, y el bucle termina de forma limpia justo después.


4.9 Ejemplo guiado: el mini-juego "Adivina el número" (modo dos jugadores)

4.9.1 Planteamiento

Vamos a construir juntos un ejemplo completo que usa exactamente lo que has aprendido en esta sección: un jugador piensa un número secreto, y el otro tiene que adivinarlo recibiendo pistas de "más alto" o "más bajo" hasta acertar.

Coherencia con el roadmap del módulo

Este mismo juego aparece formalmente como actividad evaluable en el apartado de "Ejercicios y actividades" de esta unidad. Aquí lo construimos como ejemplo guiado para fijar el concepto de do-while; cuando llegues a la actividad oficial, ya sabrás resolverla de memoria. Además, en UD2, una vez estudiada la clase Random, retomarás este mismo código como actividad de repaso espaciado, sustituyendo la introducción manual del número secreto por generación automática —sin tocar ni una línea del bucle ni de los condicionales—. Es una primera muestra, muy temprana, de que la lógica de "adivinar" es independiente de "cómo se obtiene el número a adivinar" (una semilla del principio de responsabilidad única que formalizarás en UD7).

4.9.2 Por qué do-while es la estructura correcta aquí

El jugador 2 tiene que introducir al menos un intento, siempre, sin excepción: no tiene sentido comprobar "¿ha acertado?" antes de que haya intentado adivinar por primera vez. Esa garantía de "al menos una vez" es exactamente lo que aporta el do-while, y es la razón por la que esta estructura —y no while— es la elegida en el roadmap del módulo para esta actividad.

4.9.3 Código completo comentado

import java.util.Scanner;

public class AdivinaNumeroDosJugadores {

    public static void main(String[] args) {

        final int MIN = 1;
        final int MAX = 100;

        Scanner sc = new Scanner(System.in);

        IO.println("=== Adivina el número (modo 2 jugadores) ===");
        IO.println("Jugador 1: piensa un número entre " + MIN + " y " + MAX + ".");
        IO.print("Jugador 1, introduce el número secreto: ");
        int secreto = Integer.parseInt(sc.nextLine().trim());

        IO.println();
        IO.println("Jugador 2, es tu turno. ¡Suerte!");

        int intento;
        int numeroDeIntentos = 0;

        do {
            IO.print("Jugador 2, tu intento: ");
            intento = Integer.parseInt(sc.nextLine().trim());
            numeroDeIntentos++;

            if (intento < secreto) {
                IO.println("Más alto.");
            } else if (intento > secreto) {
                IO.println("Más bajo.");
            }
        } while (intento != secreto);

        IO.println("¡Acertaste en " + numeroDeIntentos + " intentos!");
        sc.close();
    }
}

Ejecución de ejemplo (secreto = 42):

1
2
3
4
5
6
Jugador 2, tu intento: 50
Más bajo.
Jugador 2, tu intento: 25
Más alto.
Jugador 2, tu intento: 42
¡Acertaste en 3 intentos!

Puntos a observar:

  1. do-while en vez de while: el primer intento se pide siempre, sin comprobar nada antes.
  2. La condición de parada es intento != secreto: se sigue repitiendo mientras el intento no coincida.
  3. if / else if dentro del bucle (contenido de la sección 2, reutilizado sin cambios): dar la pista de "más alto" o "más bajo".
  4. numeroDeIntentos es un contador, inicializado a 0 antes del bucle e incrementado dentro, siguiendo exactamente el mismo patrón de "inicializar fuera, actualizar dentro" de la sección 4.3.3.
  5. No hay ninguna rama para "acertaste" dentro del do-while: como el else if cubre "más alto" y "más bajo", cuando intento == secreto ninguna de las dos ramas se ejecuta, el bucle simplemente evalúa su condición como false y sale, imprimiendo el mensaje final justo después.

4.9.4 Conexión con el roadmap: qué pasará en UD2

Cuando en UD2 conozcas la clase Random, la única línea que cambiará de este programa es la que pide el número secreto al jugador 1:

1
2
3
4
5
// Hoy (UD3): número secreto introducido a mano por el jugador 1
int secreto = Integer.parseInt(sc.nextLine().trim());

// Más adelante (UD2, repaso espaciado): número secreto generado automáticamente
int secreto = new Random().nextInt(MAX) + MIN;

Todo el bucle do-while, la comparación intento != secreto y los mensajes de "más alto"/"más bajo" permanecen exactamente igual. Guarda mentalmente esta comparación: es la prueba de que lo que estás aprendiendo hoy no es "un truco para un ejercicio", sino una estructura de control que sobrevive intacta aunque cambie por completo el origen de los datos.


4.10 Trampas clásicas de while y do-while

4.10.1 El punto y coma fantasma

Un ; de más, justo después de la condición de un while, crea un bucle vacío que no hace nada útil pero que puede quedarse ejecutándose para siempre:

1
2
3
4
5
6
7
// BUG: el ; convierte esto en un bucle vacío que nunca actualiza 'contador'
int contador = 0;
while (contador < 5);   // <- el cuerpo del bucle es esta sentencia vacía (el ';')
{
    IO.println("Esto NO es el cuerpo del while, es un bloque aparte");
    contador++;
}

Aquí, el ; inmediatamente después de while (contador < 5) es, gramaticalmente, el cuerpo entero del bucle: una sentencia vacía que no hace nada. Como contador nunca cambia, la condición contador < 5 es true para siempre, y el programa se queda repitiendo esa sentencia vacía infinitamente, sin llegar nunca al bloque { } que viene después (ese bloque ya no pertenece al while: es un bloque de código suelto que se ejecutaría, como mucho, una vez, si el bucle terminara alguna vez). IntelliJ suele avisarte con un aviso de "':' probably a bug" o similar; no lo ignores.

4.10.2 Olvidar el ; final del do-while

El error contrario: en do-while, olvidar el ; obligatorio tras while (condicion) es un error de compilación, no un bucle infinito silencioso:

1
2
3
4
// NO COMPILA: falta el ; tras while (condicion)
do {
    IO.println("Hola");
} while (contador < 5)

A diferencia de la trampa anterior, esta la detecta el compilador por ti. Es menos peligrosa, pero te va a frenar hasta que la corrijas, así que acostúmbrate a escribir el ; de forma automática en cuanto cierras el paréntesis de un do-while.

4.10.3 Olvidar actualizar la variable de control

Ya la viste en la sección 4.6.1, pero merece su hueco en la tabla de trampas por ser, con diferencia, la causa más común de bucle infinito:

1
2
3
4
5
6
// BUG: bucle infinito, falta contador++
int contador = 1;
while (contador <= 5) {
    IO.println("Vuelta número: " + contador);
    // Falta: contador++;
}

Regla mental: cada vez que escribas un while o un do-while, antes de darle a "Run", busca con la vista la línea que modifica la variable que aparece en la condición. Si no la encuentras, no ejecutes: acabas de encontrar un bucle infinito antes de que ocurra.

4.10.4 Off-by-one: < frente a <=

Un error de "uno de más o uno de menos" en la condición cambia cuántas veces se repite el bucle, de forma sutil:

1
2
3
4
5
6
// Intención: imprimir del 1 al 10 (10 números)
int contador = 1;
while (contador < 10) {          // BUG: con '<', el último valor impreso es 9, no 10
    IO.println(contador);
    contador++;
}

Con contador < 10, la última vuelta ocurre con contador = 9 (porque cuando contador vale 10, la condición ya es false), así que solo se imprimen los números del 1 al 9: faltan el 10. La versión correcta, si quieres incluir el 10, es contador <= 10. Este tipo de error no lanza ninguna excepción ni error de compilación: el programa "funciona", solo que hace una cosa ligeramente distinta de la que querías. Por eso el trazado a mano (sección 4.3.4) es tan valioso: te obliga a comprobar el valor exacto de la última vuelta.

4.10.5 Declarar la variable de control dentro del bucle

Si declaras dentro del bloque una variable que debería acumular su valor entre vueltas, la vuelves a crear —y a reiniciar— en cada iteración:

// BUG: 'suma' se reinicia a 0 en cada vuelta, así que nunca acumula nada
Scanner sc = new Scanner(System.in);
int numero;

do {
    int suma = 0;                          // <- declarada DENTRO: se reinicia siempre
    IO.print("Número: ");
    numero = Integer.parseInt(sc.nextLine().trim());
    suma += numero;
    IO.println("Suma acumulada (incorrecta): " + suma);   // siempre imprime el último número
} while (numero != 0);

La corrección es sencilla una vez identificado el problema: declarar suma fuera del bucle, una sola vez, como ya hiciste correctamente en la sección 4.7.4:

// CORRECTO: 'suma' declarada FUERA del bucle, se mantiene entre vueltas
Scanner sc = new Scanner(System.in);
int suma = 0;
int numero;

do {
    IO.print("Número: ");
    numero = Integer.parseInt(sc.nextLine().trim());
    suma += numero;
} while (numero != 0);

IO.println("Suma acumulada (correcta): " + suma);

Regla: si una variable tiene que "recordar" algo de una vuelta a la siguiente (un contador, una suma, un booleano de estado), declárala antes del bucle. Si una variable solo tiene sentido dentro de una única vuelta y no necesita sobrevivir a la siguiente, declárala dentro.

4.10.6 Confundir while con do-while en validaciones

Elegir la estructura equivocada no rompe la compilación, pero produce un comportamiento distinto del esperado. Por ejemplo, si usas while para un menú que "siempre debe mostrarse al menos una vez", necesitas el truco de inicialización de la sección 4.7.2; si te olvidas de él, el menú podría no mostrarse nunca:

1
2
3
4
5
6
// BUG: con 'while', si 'opcion' empieza como "3", el menú no se muestra NUNCA
String opcion = "3";        // valor inicial que "casualmente" ya cumple la condición de salida
while (!opcion.equals("3")) {
    IO.println("--- Menú ---");
    // ...
}

Con do-while, este problema desaparece por diseño, porque la primera ejecución del bloque no depende de ningún valor inicial de opcion:

1
2
3
4
5
6
7
// CORRECTO: con do-while, el menú se muestra siempre, sin necesidad de ningún truco
String opcion;
do {
    IO.println("--- Menú ---");
    // ...
    opcion = sc.nextLine().trim();
} while (!opcion.equals("3"));

4.10.7 Condición negada de forma confusa

Igual que viste con el if en la sección 2.6.7, las dobles negaciones en la condición de un bucle son difíciles de leer:

// CONFUSO: doble negación
while (!(!terminado)) {
    // ...
}

// CLARO: simplifica antes de escribir
while (terminado == false) {   // aceptable, pero mejor aún:
    // ...
}

while (!terminado) {           // la forma idiomática en Java
    // ...
}

La regla es la misma que ya conoces: nombra tus variables booleanas en positivo (terminado, esValido, continuar) y escribe la condición del bucle de la forma más directa posible, sin acumular negaciones innecesarias.

4.10.8 Resumen de trampas

Trampa Síntoma Solución
; de más tras while (condicion) Bucle vacío que se repite infinitamente sin hacer nada útil. Revisa que no haya ; justo después del paréntesis, salvo que sea intencionado.
; que falta tras while (condicion) en un do-while Error de compilación: ';' expected. Añade siempre el ; final del do-while.
Olvidar actualizar la variable de control Bucle infinito, el programa "se cuelga". Comprueba que la variable de la condición se modifica dentro del bloque.
Off-by-one (< vs <=) El bucle se ejecuta una vez de más o de menos. Traza a mano la primera y la última vuelta.
Variable acumuladora declarada dentro del bucle Se reinicia en cada vuelta, "no acumula". Declara fuera del bucle las variables que deben sobrevivir entre vueltas.
while sin truco de inicialización cuando hacía falta do-while El bloque podría no ejecutarse nunca, aunque debería hacerlo siempre. Si el bloque debe ejecutarse siempre al menos una vez, usa do-while.
Doble negación en la condición Condición difícil de leer, errores de lógica. Nombra las variables booleanas en positivo y evita !(!x).

4.11 Ejemplos resueltos paso a paso

Cuatro ejemplos completos, cada uno centrado en un patrón distinto de esta sección. Escríbelos tú mismo en IntelliJ antes de leer la explicación.

4.11.1 Ejemplo 1: suma y contador de números con centinela

import java.util.Scanner;

public class SumaConCentinela {

    public static void main(String[] args) {

        final int CENTINELA = 0;

        Scanner sc = new Scanner(System.in);
        sc.useLocale(java.util.Locale.US);

        int suma = 0;
        int cantidadDeNumeros = 0;
        int numero;

        IO.println("Introduce números (escribe " + CENTINELA + " para terminar):");

        do {
            IO.print("Número: ");
            numero = Integer.parseInt(sc.nextLine().trim());

            if (numero != CENTINELA) {
                suma += numero;
                cantidadDeNumeros++;
            }
        } while (numero != CENTINELA);

        if (cantidadDeNumeros == 0) {
            IO.println("No se introdujo ningún número.");
        } else {
            double media = (double) suma / cantidadDeNumeros;
            IO.println("Cantidad de números: " + cantidadDeNumeros);
            IO.println("Suma total: " + suma);
            IO.println(String.format("Media: %.2f", media));
        }

        sc.close();
    }
}

Puntos a observar:

  1. CENTINELA como constante final: si mañana decides que el centinela sea -1 en vez de 0, solo cambias una línea.
  2. El if (numero != CENTINELA) dentro del bucle evita sumar el propio centinela, a diferencia del ejemplo más simple de la sección 4.7.4.
  3. cantidadDeNumeros == 0 se comprueba después del bucle para evitar una división por cero al calcular la media (una anticipación del cuidado que en la sección 8 aprenderás a gestionar con excepciones).
  4. (double) suma: sin este casting, suma / cantidadDeNumeros sería una división entera (UD1) y perderías los decimales.

4.11.2 Ejemplo 2: tabla de multiplicar

import java.util.Scanner;

public class TablaDeMultiplicar {

    public static void main(String[] args) {

        final int PRIMER_FACTOR = 1;
        final int ULTIMO_FACTOR = 10;

        Scanner sc = new Scanner(System.in);

        IO.print("¿Tabla de qué número quieres ver? ");
        int numero = Integer.parseInt(sc.nextLine().trim());

        IO.println("--- Tabla del " + numero + " ---");

        int factor = PRIMER_FACTOR;
        while (factor <= ULTIMO_FACTOR) {
            IO.println(numero + " x " + factor + " = " + (numero * factor));
            factor++;
        }

        sc.close();
    }
}

Puntos a observar:

  1. while, no do-while: no hay ninguna razón para que la tabla se muestre "al menos una vez" de forma incondicional; si en el futuro cambias PRIMER_FACTOR a un valor mayor que ULTIMO_FACTOR, tiene sentido que no se imprima nada.
  2. Constantes PRIMER_FACTOR y ULTIMO_FACTOR: evitan los magic numbers 1 y 10 sueltos en la condición, siguiendo la misma buena práctica de la sección 2.

4.11.3 Ejemplo 3: cajero automático con intentos limitados de PIN

import java.util.Scanner;

public class CajeroPin {

    public static void main(String[] args) {

        final int PIN_CORRECTO = 1234;
        final int MAX_INTENTOS = 3;

        Scanner sc = new Scanner(System.in);

        int intentos = 0;
        int pinIntroducido;
        boolean acceso = false;

        do {
            IO.print("Introduce tu PIN: ");
            pinIntroducido = Integer.parseInt(sc.nextLine().trim());
            intentos++;

            if (pinIntroducido == PIN_CORRECTO) {
                acceso = true;
            } else {
                IO.println("PIN incorrecto. Intentos restantes: " + (MAX_INTENTOS - intentos));
            }
        } while (!acceso && intentos < MAX_INTENTOS);

        if (acceso) {
            IO.println("Acceso concedido. Bienvenido.");
        } else {
            IO.println("Cuenta bloqueada por seguridad tras " + MAX_INTENTOS + " intentos fallidos.");
        }

        sc.close();
    }
}

Puntos a observar:

  1. do-while: siempre hay que pedir el PIN al menos una vez.
  2. Condición compuesta !acceso && intentos < MAX_INTENTOS: el bucle sigue mientras no se haya concedido acceso y todavía queden intentos. En cuanto cualquiera de las dos deje de cumplirse —acierta el PIN, o se agotan los intentos—, el bucle termina.
  3. Dos posibles motivos de salida del bucle, distinguidos después con un if (acceso): acertar el PIN o agotar los intentos. El bucle en sí no "sabe" por qué terminó; hace falta comprobarlo después con una variable de estado (acceso), un patrón muy habitual.

4.11.4 Ejemplo 4: calculadora con menú repetido

import java.util.Scanner;

public class CalculadoraConMenu {

    public static void main(String[] args) {

        final String OPCION_SALIR = "5";

        Scanner sc = new Scanner(System.in);
        sc.useLocale(java.util.Locale.US);
        String opcion;

        do {
            IO.println("=== Calculadora ===");
            IO.println("1. Sumar");
            IO.println("2. Restar");
            IO.println("3. Multiplicar");
            IO.println("4. Dividir");
            IO.println("5. Salir");
            IO.print("Elige una opción: ");
            opcion = sc.nextLine().trim();

            if (opcion.equals(OPCION_SALIR)) {
                IO.println("Hasta luego.");
            } else if (opcion.matches("[1-4]")) {
                IO.print("Operando A: ");
                double a = Double.parseDouble(sc.nextLine().trim().replace(',', '.'));
                IO.print("Operando B: ");
                double b = Double.parseDouble(sc.nextLine().trim().replace(',', '.'));

                if (opcion.equals("1")) {
                    IO.println("Resultado: " + (a + b));
                } else if (opcion.equals("2")) {
                    IO.println("Resultado: " + (a - b));
                } else if (opcion.equals("3")) {
                    IO.println("Resultado: " + (a * b));
                } else {
                    if (b == 0) {
                        IO.println("Error: no se puede dividir por cero.");
                    } else {
                        IO.println("Resultado: " + (a / b));
                    }
                }
            } else {
                IO.println("Opción no válida.");
            }

            IO.println();
        } while (!opcion.equals(OPCION_SALIR));

        sc.close();
    }
}

Puntos a observar:

  1. do-while para el menú: se muestra siempre, al menos una primera vez.
  2. opcion.matches("[1-4]") valida de un vistazo que la opción esté en el rango de operaciones (regex ya vista en la sección 2.8.1).
  3. IO.println(); sin argumentos, al final del bloque, imprime una línea en blanco para separar visualmente cada vuelta del menú.
  4. La condición final !opcion.equals(OPCION_SALIR) es la única que decide si el bucle continúa; el resto de la lógica (sumar, restar...) vive dentro del bloque, sin influir en la condición.

4.12 Buenas prácticas y reglas de oro

4.12.1 Las 8 reglas del bucle profesional

  1. Inicializa la variable de control antes del bucle, nunca dentro (salvo que quieras que se reinicie cada vuelta a propósito).
  2. Actualiza la variable de control dentro del bloque, sin excepciones. Si no lo haces, tienes un bucle infinito.
  3. Vigila el ;: nunca lo pongas justo después de while (condicion) en un while normal; nunca lo olvides al final de un do-while.
  4. Si la acción debe ejecutarse siempre al menos una vez, usa do-while. Si podría no hacer falta nunca, usa while.
  5. Nombra las variables booleanas en positivo (terminado, esValido, continuar) para que la condición del bucle se lea como una frase natural.
  6. Usa while (true) + break solo cuando el patrón loop-and-a-half lo justifique de verdad; no lo conviertas en la forma por defecto de escribir todos tus bucles.
  7. Comenta el propósito del bucle si su condición no es evidente a simple vista (por ejemplo, un centinela poco intuitivo).
  8. Traza a mano la primera y la última vuelta antes de ejecutar, sobre todo si sospechas un error de tipo off-by-one.

4.12.2 Cuándo usar while, cuándo do-while (y cuándo esperar al for)

Situación Estructura recomendada
El bloque podría no hacer falta ejecutarlo nunca (depende del estado inicial). while.
El bloque tiene que ejecutarse siempre al menos una vez (menús, pedir un dato). do-while.
Conoces de antemano exactamente cuántas veces hay que repetir algo (por ejemplo, "del 1 al 10"). Espera a la sección 5 (for clásico); while también funciona, pero for lo expresa de forma más compacta y menos propensa a errores de inicialización/actualización.
Necesitas leer un dato antes de decidir si sigues, y no encaja limpiamente ni al principio ni al final. while (true) + break (loop-and-a-half).

4.12.3 Comentar los bucles

Igual que con el if en la sección 2.9.3, un buen comentario en un bucle explica por qué, no qué:

// MAL: repite lo que ya dice el código
while (contador <= 10) {   // mientras contador sea menor o igual que 10
    contador++;
}

// BIEN: explica una decisión que el código no puede expresar por sí solo
final int MAX_REINTENTOS_CONEXION = 10;   // límite acordado con el equipo de redes (2025)
while (intentosConexion < MAX_REINTENTOS_CONEXION) {
    // ...
}

4.13 Resumen y mapa conceptual

4.13.1 Mapa conceptual

mindmap
  root((Sección 4 - while y do-while))
    while
      Precondición
      Puede ejecutarse 0 veces
      Trazado paso a paso
    do-while
      Poscondición
      Se ejecuta al menos 1 vez
      Punto y coma final obligatorio
    Bucles infinitos
      Accidental: olvidar actualizar
      Intencionado: while true mas break
      Loop and a half
    Validación con Scanner
      Pedir hasta que sea válido
      Pedir hasta que esté en rango
      Centinela para acumular
    Menús interactivos
      do-while como estructura natural
    Trampas
      Punto y coma fantasma
      Variable acumuladora dentro del bucle
      Off-by-one
      while sin truco de inicialización

4.13.2 Las 10 ideas que no debes olvidar

  1. while comprueba la condición ANTES de cada vuelta. Puede ejecutarse 0 veces.
  2. do-while comprueba la condición DESPUÉS de cada vuelta. Se ejecuta siempre al menos 1 vez.
  3. El do-while termina con ; obligatorio tras while (condicion); el while no lo lleva.
  4. Inicializa fuera, actualiza dentro. La variable de control se declara antes del bucle y se modifica dentro de él.
  5. La causa más común de bucle infinito es olvidar actualizar la variable de control.
  6. while (true) + break es un patrón legítimo para el caso loop-and-a-half, no un error.
  7. break ya lo conocías del switch (sección 3): en un bucle hace exactamente lo mismo, salir inmediatamente de la estructura que lo contiene.
  8. Con bucles ya puedes volver a preguntar cuando un dato no es válido, algo que en la sección 2 no era posible.
  9. El centinela es un valor especial que marca "he terminado", distinto de los datos que se están procesando de verdad.
  10. Si dudas entre while y do-while, pregúntate: "¿esto tiene que pasar siempre, al menos una vez?" Si la respuesta es sí, do-while.

4.13.3 Glosario rápido

Término Significado
Iteración Repetición de un bloque de código mientras se cumpla una condición.
while Bucle con precondición: comprueba antes de ejecutar el bloque.
do-while Bucle con poscondición: ejecuta el bloque una vez y comprueba después.
Precondición Condición que se evalúa antes de entrar al bloque.
Poscondición Condición que se evalúa después de ejecutar el bloque.
Variable de control Variable que determina si el bucle continúa o termina.
Bucle infinito Bucle cuya condición nunca llega a ser false.
Loop-and-a-half Patrón while (true) + break para cuando la condición de salida solo se conoce a mitad del bloque.
Centinela Valor especial introducido por el usuario para indicar el fin de una serie de datos.
Off-by-one Error de lógica en el que el bucle se ejecuta una vez de más o de menos.
Trazado (o traza) Técnica de seguir a mano, tabla a tabla, el valor de las variables en cada vuelta de un bucle.

4.14 Actividades propuestas

Cinco actividades graduadas, en orden de dificultad creciente. Hazlas en este orden. Las soluciones se publicarán en Aules cuando el profesor las entregue.

4.14.1 Actividad 1 — Contador de pares e impares

Objetivo: practicar el patrón de centinela con do-while y acumuladores.

Enunciado: Escribe un programa ContadorParesImpares que lea números enteros hasta que el usuario escriba -1 (centinela) y muestre, al terminar:

  • Cuántos números pares se introdujeron (sin contar el centinela).
  • Cuántos números impares se introdujeron.
  • La suma de todos los números pares.

Requisitos:

  • Usa do-while, ya que como mínimo se pide un número.
  • Usa una constante final int CENTINELA = -1;.
  • No cuentes ni sumes el centinela.
  • Cierra el Scanner al final.

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

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

4.14.2 Actividad 2 — Validador robusto de nota

Objetivo: practicar la validación en bucle con while y el truco de inicialización.

Enunciado: Escribe un programa ValidadorNota que pida una nota entre 0 y 10 (admite decimales), repitiendo la pregunta hasta que el usuario introduzca un valor numérico y dentro de rango. Una vez validada, clasifícala reutilizando la lógica de if/else if de la sección 2 (Suspenso, Aprobado, Notable, Sobresaliente).

Requisitos:

  • Usa while, con el truco de inicialización visto en la sección 4.7.2 (o do-while, si justificas por qué también es una elección válida en un comentario).
  • Valida el formato con .matches("\\d+(\\.\\d+)?") antes de convertir con Double.parseDouble.
  • Si el formato no es válido, muestra un error específico distinto del error de "fuera de rango".

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

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

4.14.3 Actividad 3 — Cajero con reintentos personalizados

Objetivo: combinar do-while con una condición compuesta y dos motivos de salida distintos.

Enunciado: Parte del ejemplo CajeroPin de la sección 4.11.3 y amplíalo:

  • El número máximo de intentos ahora se pide al usuario al principio (entre 1 y 5; valida ese dato también).
  • Si el usuario acierta el PIN, muestra en qué intento lo consiguió ("Acceso concedido en el intento 2 de 3.").
  • Si agota los intentos, muestra un mensaje distinto indicando que la cuenta queda bloqueada.

Requisitos:

  • Usa una variable booleana de estado (como acceso en el ejemplo original) para distinguir, después del bucle, por qué terminó.
  • Usa constantes final donde corresponda.

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

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

4.14.4 Actividad 4 — Conversor de unidades con menú

Objetivo: practicar el patrón de menú repetido con do-while.

Enunciado: Escribe un programa ConversorUnidades con un menú de opciones:

Opción Conversión
1 Celsius a Fahrenheit (F = C * 9/5 + 32)
2 Kilómetros a millas (millas = km * 0.621371)
3 Kilogramos a libras (libras = kg * 2.20462)
4 Salir

Requisitos:

  • El menú se muestra siempre al menos una vez, y se repite hasta que el usuario elige "Salir".
  • Valida que la opción elegida sea una de las cuatro disponibles; si no, muestra un error y vuelve a mostrar el menú (sin cerrar el programa).
  • Muestra los resultados con dos decimales usando String.format("%.2f", valor).

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

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

4.14.5 Actividad 5 — Cuestionario de repaso

Responde por escrito (no hace falta compilar):

  1. ¿Cuál es la diferencia esencial entre while y do-while?
  2. ¿Por qué un do-while necesita un ; al final y un while no?
  3. Explica con tus palabras qué significa que el while tiene "precondición" y el do-while "poscondición".
  4. ¿Qué tres piezas tiene que llevar siempre un bucle bien construido para no ser infinito por accidente?
  5. Si necesitas mostrar un menú al usuario que se repita hasta que elija "salir", ¿usarías while o do-while? Justifica la respuesta.
  6. ¿Qué es un valor centinela? Pon un ejemplo distinto a los vistos en la sección.
  7. Explica qué es el patrón loop-and-a-half y por qué while (true) + break no es "hacer trampa", sino una técnica legítima.
  8. ¿Qué error de lógica (no de compilación) provoca escribir contador < 10 en vez de contador <= 10 cuando quieres llegar hasta el 10 incluido?
  9. ¿Por qué declarar dentro del bucle una variable que debería acumular un valor (como una suma) es un error habitual? ¿Cómo se soluciona?
  10. Nombra dos de las ocho reglas del "bucle profesional" que aplicarás a partir de hoy.

Entrega: archivo cuestionario_seccion4.md con tus respuestas.

Criterios evaluados: CE 3.2, CE 3.7.


4.15 Lo que viene en la sección 5

Ya dominas la iteración controlada por condición: sabes escribir while y do-while, elegir entre ambos según si la acción debe ocurrir siempre al menos una vez, evitar los bucles infinitos accidentales y aprovechar los intencionados, y validar entrada de usuario en bucle. En la sección 5 vas a conocer la tercera estructura de repetición de Java, la más usada de todas cuando sabes de antemano cuántas veces hay que repetir algo: el for clásico.

Aprenderás:

  • La anatomía del for: inicialización, condición y actualización, las tres reunidas en una sola línea.
  • Variantes del for: decreciente, con saltos de más de uno en uno, con múltiples variables de control.
  • El for aplicado a índices de arrays y de String (recorrer carácter a carácter).
  • El criterio de elección for frente a while: cuándo cada uno expresa mejor tu intención.

Cuando termines la sección 5, tendrás las tres estructuras de repetición "clásicas" de Java. En la sección 6 conocerás una cuarta forma, pensada específicamente para colecciones (for-each), y en la sección 7 aprenderás a combinar bucles anidados con las sentencias de salto (break, continue, return) en profundidad.


Cierre del profesor. Si la sección 2 fue el momento en que tu programa empezó a "decidir", esta sección 4 es el momento en que tu programa empieza a persistir. Ya no escribes diez líneas para hacer diez cosas: escribes una condición y dejas que Java repita por ti, tantas veces como haga falta, ni una más ni una menos. Has visto que la diferencia entre while y do-while no es un capricho sintáctico, sino una decisión de diseño con consecuencias reales: "puede que no haga falta nunca" frente a "tiene que pasar sí o sí, al menos una vez". Y has aprendido, por las malas —como se aprende siempre esto la primera vez—, que un bucle sin actualización de su variable de control es una promesa de bucle infinito. Guarda ese hábito de comprobar, antes de ejecutar, que tu bucle tiene sus tres piezas: inicialización, condición y actualización. Nos vemos en la sección 5, donde vas a descubrir que Java tiene una forma de escribir esas tres piezas en una sola línea, y por qué esa forma se ha convertido en la más usada del lenguaje para repetir un número conocido de veces.