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:
- Por qué hace falta la iteración: el problema de repetir código a mano.
- El bucle
while: sintaxis, anatomía, y su propiedad más importante —puede no ejecutarse nunca—. - El bucle
do-while: sintaxis, anatomía, y su propiedad más importante —se ejecuta siempre al menos una vez—. - La comparativa entre ambos y el criterio para elegir uno u otro.
- Los bucles infinitos: el peligro del accidental y la utilidad del intencionado con
while (true)+break. - 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. - Los menús interactivos, el caso de uso más natural de
do-while. - Un ejemplo guiado completo: el mini-juego "Adivina el número".
- Las trampas clásicas de todo bucle, empezando por el temido bucle infinito accidental.
- 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:
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
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
contadorsea 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
Salida:
Fíjate en tres piezas imprescindibles, que siempre van juntas en un while bien construido:
- Inicialización:
int contador = 1;— se declara antes del bucle, fuera de él. - Condición:
contador <= 5— se comprueba antes de cada vuelta. - 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.
Salida:
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)
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.
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.
Salida:
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
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. | Sí, 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:
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 sí (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:
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) oCmd+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.
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:
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
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:
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:
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
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
Ejecución de ejemplo (secreto = 42):
Puntos a observar:
do-whileen vez dewhile: el primer intento se pide siempre, sin comprobar nada antes.- La condición de parada es
intento != secreto: se sigue repitiendo mientras el intento no coincida. if/else ifdentro del bucle (contenido de la sección 2, reutilizado sin cambios): dar la pista de "más alto" o "más bajo".numeroDeIntentoses 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.- No hay ninguna rama para "acertaste" dentro del
do-while: como elelse ifcubre "más alto" y "más bajo", cuandointento == secretoninguna de las dos ramas se ejecuta, el bucle simplemente evalúa su condición comofalsey 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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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
Puntos a observar:
CENTINELAcomo constantefinal: si mañana decides que el centinela sea-1en vez de0, solo cambias una línea.- 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. cantidadDeNumeros == 0se 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).(double) suma: sin este casting,suma / cantidadDeNumerossería una división entera (UD1) y perderías los decimales.
4.11.2 Ejemplo 2: tabla de multiplicar
Puntos a observar:
while, nodo-while: no hay ninguna razón para que la tabla se muestre "al menos una vez" de forma incondicional; si en el futuro cambiasPRIMER_FACTORa un valor mayor queULTIMO_FACTOR, tiene sentido que no se imprima nada.- Constantes
PRIMER_FACTORyULTIMO_FACTOR: evitan los magic numbers1y10sueltos 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
Puntos a observar:
do-while: siempre hay que pedir el PIN al menos una vez.- 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. - 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
Puntos a observar:
do-whilepara el menú: se muestra siempre, al menos una primera vez.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).IO.println();sin argumentos, al final del bloque, imprime una línea en blanco para separar visualmente cada vuelta del menú.- 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
- Inicializa la variable de control antes del bucle, nunca dentro (salvo que quieras que se reinicie cada vuelta a propósito).
- Actualiza la variable de control dentro del bloque, sin excepciones. Si no lo haces, tienes un bucle infinito.
- Vigila el
;: nunca lo pongas justo después dewhile (condicion)en unwhilenormal; nunca lo olvides al final de undo-while. - Si la acción debe ejecutarse siempre al menos una vez, usa
do-while. Si podría no hacer falta nunca, usawhile. - Nombra las variables booleanas en positivo (
terminado,esValido,continuar) para que la condición del bucle se lea como una frase natural. - Usa
while (true)+breaksolo 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. - Comenta el propósito del bucle si su condición no es evidente a simple vista (por ejemplo, un centinela poco intuitivo).
- 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é:
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
whilecomprueba la condición ANTES de cada vuelta. Puede ejecutarse 0 veces.do-whilecomprueba la condición DESPUÉS de cada vuelta. Se ejecuta siempre al menos 1 vez.- El
do-whiletermina con;obligatorio traswhile (condicion); elwhileno lo lleva. - Inicializa fuera, actualiza dentro. La variable de control se declara antes del bucle y se modifica dentro de él.
- La causa más común de bucle infinito es olvidar actualizar la variable de control.
while (true)+breakes un patrón legítimo para el caso loop-and-a-half, no un error.breakya lo conocías delswitch(sección 3): en un bucle hace exactamente lo mismo, salir inmediatamente de la estructura que lo contiene.- Con bucles ya puedes volver a preguntar cuando un dato no es válido, algo que en la sección 2 no era posible.
- El centinela es un valor especial que marca "he terminado", distinto de los datos que se están procesando de verdad.
- Si dudas entre
whileydo-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
Scanneral 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 (odo-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 conDouble.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
accesoen el ejemplo original) para distinguir, después del bucle, por qué terminó. - Usa constantes
finaldonde 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):
- ¿Cuál es la diferencia esencial entre
whileydo-while? - ¿Por qué un
do-whilenecesita un;al final y unwhileno? - Explica con tus palabras qué significa que el
whiletiene "precondición" y eldo-while"poscondición". - ¿Qué tres piezas tiene que llevar siempre un bucle bien construido para no ser infinito por accidente?
- Si necesitas mostrar un menú al usuario que se repita hasta que elija "salir", ¿usarías
whileodo-while? Justifica la respuesta. - ¿Qué es un valor centinela? Pon un ejemplo distinto a los vistos en la sección.
- Explica qué es el patrón loop-and-a-half y por qué
while (true)+breakno es "hacer trampa", sino una técnica legítima. - ¿Qué error de lógica (no de compilación) provoca escribir
contador < 10en vez decontador <= 10cuando quieres llegar hasta el 10 incluido? - ¿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?
- 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
foraplicado a índices de arrays y deString(recorrer carácter a carácter). - El criterio de elección
forfrente awhile: 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
whileydo-whileno 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.