Módulo 06: Estructuras repetitivas

Objetivos del módulo

  • Entender qué es una iteración y las cuatro partes de todo bucle
  • Usar while, do-while y for, y saber elegir el adecuado en cada caso
  • Leer datos hasta un valor centinela y validar la entrada del usuario
  • Aplicar los patrones clásicos: contadores, acumuladores, máximos y mínimos, y banderas
  • Controlar el flujo con break y continue (y conocer las etiquetas)
  • Detectar y evitar los bucles infinitos
  • Escribir bucles anidados para tablas y figuras
  • Hacer trazas de bucles a mano
  • Programar los algoritmos clásicos: primos, factorial, Fibonacci, MCD, números perfectos…

1. La iteración

Imagina que tienes que escribir los números del 1 al 1000. Con lo que sabes hasta ahora necesitarías mil println. Y si el usuario decide cuántos números quiere, ni siquiera podrías escribir el programa. Para eso están los bucles.

📘 Concepto: Un bucle (o estructura repetitiva) ejecuta un bloque de instrucciones varias veces mientras se cumpla una condición. Cada ejecución del bloque se llama iteración o vuelta.

Todo bucle bien construido tiene cuatro partes, aunque cada tipo de bucle las coloque en un sitio distinto:

Parte Qué hace Ejemplo
① Inicialización Da el valor inicial a la variable que controla el bucle int contador = 5;
② Condición Decide si se da otra vuelta contador > 0
③ Cuerpo Las instrucciones que se repiten System.out.println(contador);
④ Actualización Modifica la variable de control para acercarse al final contador--;

Si falta la actualización, o no acerca la variable al final, la condición nunca se vuelve falsa y obtienes un bucle infinito.

Java tiene tres bucles, que corresponden a las tres estructuras repetitivas que viste en el módulo 02:

Pseudocódigo Java Comprueba la condición
Mientras ... Hacer ... FinMientras while Al principio (0 o más vueltas)
Repetir ... Hasta Que ... do-while Al final (1 o más vueltas)
Para i <- a Hasta b Con Paso p Hacer ... FinPara for Al principio (número de vueltas conocido)

2. El bucle while

while («mientras») repite el cuerpo mientras la condición sea verdadera. La condición se comprueba antes de cada vuelta: si es falsa desde el principio, el cuerpo no se ejecuta ni una vez.

Diagrama de flujo de un bucle while que cuenta hacia atrás de 5 a 1

Figura 1. Diagrama de flujo del bucle while con sus cuatro partes numeradas.

Algoritmo CuentaAtras
    contador <- 5
    Mientras contador > 0 Hacer
        Escribir contador
        contador <- contador - 1
    FinMientras
    Escribir "¡Despegue!"
FinAlgoritmo
public class CuentaAtras {
    public static void main(String[] args) {
        int contador = 5;                 // 1) inicialización

        while (contador > 0) {            // 2) condición
            System.out.println(contador); // 3) cuerpo
            contador--;                   // 4) actualización
        }

        System.out.println("¡Despegue!");
    }
}
5
4
3
2
1
¡Despegue!

⚠️ Importante: Igual que con el if, no pongas ; después de while (condición). while (contador > 0); es un bucle con cuerpo vacío que, como nunca cambia contador, no termina jamás.

2.1. Bucle con centinela

Muchas veces no sabes cuántos datos va a introducir el usuario. La solución es acordar un valor centinela: un valor especial que no es un dato válido y que indica «ya he terminado».

📘 Concepto: El patrón del centinela tiene una estructura fija: una lectura antes del bucle (para tener algo que comprobar) y otra al final del cuerpo (para la siguiente vuelta). Así el centinela nunca se procesa como si fuera un dato.

import java.util.Scanner;

public class SumaCentinela {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int suma = 0;
        int cantidad = 0;

        System.out.print("Número (0 para terminar): ");
        int n = sc.nextInt();            // lectura ANTES del bucle

        while (n != 0) {                 // 0 es el valor centinela
            suma += n;
            cantidad++;
            System.out.print("Número (0 para terminar): ");
            n = sc.nextInt();            // lectura al FINAL del cuerpo
        }

        System.out.println("Has introducido " + cantidad + " números.");
        System.out.println("Suma: " + suma);
        if (cantidad > 0) {
            System.out.printf("Media: %.2f%n", (double) suma / cantidad);
        }
    }
}
Número (0 para terminar): 5
Número (0 para terminar): 12
Número (0 para terminar): -3
Número (0 para terminar): 8
Número (0 para terminar): 0
Has introducido 4 números.
Suma: 22
Media: 5.50

El if (cantidad > 0) evita dividir entre cero si el usuario escribe 0 a la primera. Y el cast (double) suma / cantidad es necesario para que la división no sea entera (módulo 03).

2.2. Validar la entrada

El while es ideal para insistir hasta que el usuario introduzca un dato correcto:

import java.util.Scanner;

public class ValidarNota {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.print("Nota (0-10): ");
        int nota = sc.nextInt();
        while (nota < 0 || nota > 10) {
            System.out.print("Nota no válida. Repite (0-10): ");
            nota = sc.nextInt();
        }

        System.out.println("Nota registrada: " + nota);
    }
}
Nota (0-10): 12
Nota no válida. Repite (0-10): -1
Nota no válida. Repite (0-10): 7
Nota registrada: 7

Fíjate en que es el mismo patrón de lectura antes del bucle y al final del cuerpo. La condición del while describe cuándo el dato es incorrecto, porque es entonces cuando hay que repetir.


3. El bucle do-while

do-while («hacer… mientras») ejecuta primero el cuerpo y después comprueba la condición. Por eso el cuerpo se ejecuta al menos una vez. Es perfecto para validar entradas y para los menús, donde siempre hay que mostrar las opciones antes de saber si el usuario quiere salir.

Diagrama de flujo de un bucle do-while que pide la edad hasta que es válida

Figura 2. Diagrama de flujo del bucle do-while.

Algoritmo ValidarEdad
    Repetir
        Escribir "Edad (0-120): "
        Leer edad
    Hasta Que edad >= 0 Y edad <= 120
    Escribir "Edad correcta: ", edad
FinAlgoritmo
import java.util.Scanner;

public class ValidarEdad {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int edad;

        do {
            System.out.print("Edad (0-120): ");
            edad = sc.nextInt();
        } while (edad < 0 || edad > 120);   // repetir MIENTRAS no sea válida

        System.out.println("Edad correcta: " + edad);
    }
}
Edad (0-120): -5
Edad (0-120): 200
Edad (0-120): 30
Edad correcta: 30

Comparado con la versión while de la sección anterior, aquí solo hay una lectura, dentro del bucle. El código es más corto.

⚠️ Importante: Dos detalles que causan muchos errores:

  1. El do-while termina con punto y coma: } while (condición);.
  2. En PSeInt la estructura es Repetir ... Hasta Que y la condición es la de salida (cuándo parar). En Java, la condición del do-while es la de permanencia (cuándo seguir). Al traducir, hay que negar la condición: Hasta Que edad >= 0 Y edad <= 120 se convierte en while (edad < 0 || edad > 120), aplicando De Morgan (módulo 05).

💡 Consejo: La variable que se usa en la condición (edad) debe declararse fuera del do-while. Si la declaras dentro de las llaves, no existe en la línea del while y el programa no compila (recuerda el ámbito de los bloques del módulo 05).


4. El bucle for

Cuando sabes cuántas vueltas hay que dar (del 1 al 10, una vez por alumno, tantas veces como diga el usuario), el bucle más cómodo es for, porque reúne en una sola línea la inicialización, la condición y la actualización.

for (inicialización; condición; actualización) {
    cuerpo
}

Las cuatro partes de un for y su diagrama de flujo equivalente

Figura 3. Partes del bucle for y orden en que se ejecutan.

Algoritmo TablaMultiplicar
    Leer n
    Para i <- 1 Hasta 10 Con Paso 1 Hacer
        Escribir n, " x ", i, " = ", n * i
    FinPara
FinAlgoritmo
import java.util.Scanner;

public class TablaMultiplicar {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("¿Qué tabla quieres? ");
        int n = sc.nextInt();

        for (int i = 1; i <= 10; i++) {
            System.out.printf("%d x %2d = %3d%n", n, i, n * i);
        }
    }
}
¿Qué tabla quieres? 7
7 x  1 =   7
7 x  2 =  14
7 x  3 =  21
7 x  4 =  28
7 x  5 =  35
7 x  6 =  42
7 x  7 =  49
7 x  8 =  56
7 x  9 =  63
7 x 10 =  70

El orden exacto de ejecución es: ① una sola vez; luego ② → ③ → ④ → ② → ③ → ④ … hasta que ② sea falsa. Un for es, en realidad, un while escrito de forma compacta. Estos dos fragmentos son equivalentes:

for (int i = 1; i <= 10; i++) {          int i = 1;
    System.out.println(i);               while (i <= 10) {
}                                            System.out.println(i);
                                             i++;
                                         }

4.1. Sentido, incrementos y saltos

La actualización puede ser cualquier expresión: contar hacia atrás, de 2 en 2, multiplicar…

public class ForBasico {
    public static void main(String[] args) {
        // Ascendente: del 1 al 10
        for (int i = 1; i <= 10; i++) {
            System.out.print(i + " ");
        }
        System.out.println();

        // Descendente: del 10 al 1
        for (int i = 10; i >= 1; i--) {
            System.out.print(i + " ");
        }
        System.out.println();

        // De 2 en 2: los pares hasta 20
        for (int i = 2; i <= 20; i += 2) {
            System.out.print(i + " ");
        }
        System.out.println();

        // De 5 en 5 hacia atrás
        for (int i = 50; i >= 0; i -= 5) {
            System.out.print(i + " ");
        }
        System.out.println();

        // Multiplicando: potencias de 2 menores que 1000
        for (int i = 1; i < 1000; i *= 2) {
            System.out.print(i + " ");
        }
        System.out.println();
    }
}
1 2 3 4 5 6 7 8 9 10 
10 9 8 7 6 5 4 3 2 1 
2 4 6 8 10 12 14 16 18 20 
50 45 40 35 30 25 20 15 10 5 0 
1 2 4 8 16 32 64 128 256 512 

💡 Consejo: Por convención, las variables de los bucles se llaman i, j, k. Pero si el bucle recorre algo con significado, un nombre descriptivo (fila, mes, alumno) hace el código mucho más claro.

4.2. Ámbito de la variable del for

La variable declarada en la inicialización (int i = 1) solo existe dentro del for. Intentar usarla después es un error:

public class AmbitoFor {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            System.out.println(i);
        }
        System.out.println("Al salir, i vale " + i);
    }
}
AmbitoFor.java:6: error: cannot find symbol
        System.out.println("Al salir, i vale " + i);
                                                 ^
  symbol:   variable i
  location: class AmbitoFor
1 error

Si necesitas el valor final, declara la variable antes del bucle: int i; for (i = 0; i < 5; i++) { ... }.

4.3. for con varias variables

En la inicialización y en la actualización puedes poner varias expresiones separadas por comas (todas las variables declaradas deben ser del mismo tipo):

public class ForVariasVariables {
    public static void main(String[] args) {
        // i sube desde 0 y j baja desde 10 hasta que se cruzan
        for (int i = 0, j = 10; i < j; i++, j--) {
            System.out.println("i = " + i + ", j = " + j + ", suma = " + (i + j));
        }
    }
}
i = 0, j = 10, suma = 10
i = 1, j = 9, suma = 10
i = 2, j = 8, suma = 10
i = 3, j = 7, suma = 10
i = 4, j = 6, suma = 10

Útil, por ejemplo, para recorrer algo desde los dos extremos a la vez, como harás en el módulo 08 para invertir un array.

📘 Concepto: En el módulo 08 verás una segunda forma del for, el for-each (for (int nota : notas)), pensada para recorrer arrays y colecciones.


5. Trazas de bucles

Una traza es la ejecución del programa «a mano», anotando en una tabla el valor de cada variable en cada vuelta. Es la mejor forma de entender un bucle y de encontrar errores, y un tipo de pregunta muy frecuente en los exámenes.

Traza del cálculo del factorial de 5 (long f = 1; for (int i = 2; i <= 5; i++) { f *= i; }):

Vuelta i ¿i <= 5? f después de f *= i
(antes) 2 — 1
1 2 sí 2
2 3 sí 6
3 4 sí 24
4 5 sí 120
— 6 no: termina 120

Observa la última fila: la condición se comprueba una vez más que el número de vueltas, y al salir i vale 6, no 5.

💡 Consejo: IntelliJ IDEA te permite hacer trazas automáticamente con el depurador: haz clic en el margen izquierdo de una línea para poner un punto de ruptura (breakpoint), ejecuta con Debug y avanza línea a línea con Step Over (F8) viendo cómo cambian las variables. Lo estudiarás a fondo en el módulo 19, pero empieza a usarlo ya.


6. Contadores, acumuladores, máximos y mínimos

Casi todos los bucles que procesan datos usan alguno de estos patrones:

Patrón Para qué sirve Inicialización En cada vuelta
Contador Contar cuántas veces pasa algo int c = 0; c++; (quizá dentro de un if)
Acumulador de suma Sumar valores double suma = 0; suma += valor;
Acumulador de producto Multiplicar valores long prod = 1; prod *= valor;
Máximo Quedarse con el mayor primer valor (o Integer.MIN_VALUE) if (valor > max) max = valor;
Mínimo Quedarse con el menor primer valor (o Integer.MAX_VALUE) if (valor < min) min = valor;

⚠️ Importante: Un acumulador de producto se inicializa a 1, no a 0 (cualquier número multiplicado por 0 da 0). Y nunca inicialices el máximo a 0: si todos los datos fueran negativos, el resultado sería 0, que ni siquiera es un dato.

El siguiente programa usa los cuatro patrones a la vez:

import java.util.Scanner;

public class EstadisticasNotas {
    public static void main(String[] args) {
        final int ALUMNOS = 6;
        Scanner sc = new Scanner(System.in);

        int aprobados = 0;          // contador
        double suma = 0;            // acumulador de suma
        double maxima = 0, minima = 0;

        System.out.print("Introduce las " + ALUMNOS + " notas: ");
        for (int i = 1; i <= ALUMNOS; i++) {
            double nota = sc.nextDouble();

            suma += nota;
            if (nota >= 5) {
                aprobados++;
            }
            if (i == 1) {           // la primera nota es a la vez máximo y mínimo
                maxima = nota;
                minima = nota;
            } else if (nota > maxima) {
                maxima = nota;
            } else if (nota < minima) {
                minima = nota;
            }
        }

        System.out.println("Aprobados: " + aprobados + " de " + ALUMNOS);
        System.out.printf("Media: %.2f%n", suma / ALUMNOS);
        System.out.println("Nota máxima: " + maxima);
        System.out.println("Nota mínima: " + minima);
    }
}
Introduce las 6 notas: 7 4.5 9 3 6.5 8
Aprobados: 4 de 6
Media: 6.33
Nota máxima: 9.0
Nota mínima: 3.0

Hemos leído las seis notas en una sola línea separadas por espacios: nextDouble() las va tomando de una en una en cada vuelta. La primera vuelta (i == 1) inicializa el máximo y el mínimo con el primer dato, que es la técnica más segura.

💡 Consejo: En el else if (nota < minima) hay una pequeña optimización: si una nota es el nuevo máximo, no puede ser a la vez el nuevo mínimo (salvo en la primera vuelta, que ya hemos tratado aparte).


7. Banderas (flags)

📘 Concepto: Una bandera es una variable boolean que «se levanta» (pasa a true) cuando ocurre algo durante el bucle. Al terminar, la consultamos para saber si ese algo ocurrió al menos una vez.

import java.util.Scanner;

public class Bandera {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        boolean hayNegativo = false;   // bandera: empieza "bajada"
        int posicion = 0;

        System.out.print("Introduce 5 números: ");
        for (int i = 1; i <= 5; i++) {
            int n = sc.nextInt();
            if (n < 0 && !hayNegativo) {
                hayNegativo = true;    // se "levanta" la bandera
                posicion = i;
            }
        }

        if (hayNegativo) {
            System.out.println("Hay negativos. El primero está en la posición " + posicion);
        } else {
            System.out.println("Todos los números son positivos o cero.");
        }
    }
}
Introduce 5 números: 3 8 -2 7 -1
Hay negativos. El primero está en la posición 3

La condición !hayNegativo hace que solo se guarde la posición del primer negativo. Las banderas también sirven para controlar un bucle: while (!encontrado && ...), como verás en el juego de adivinar y en el test de primalidad.

💡 Consejo: Da a las banderas nombres que se lean como preguntas de sí o no: hayNegativo, encontrado, esPrimo, terminado. Así if (encontrado) se entiende sin comentarios.


8. break y continue

  • break: sale del bucle inmediatamente, sin terminar la vuelta actual ni comprobar la condición. (Es el mismo break del switch.)
  • continue: abandona la vuelta actual y salta a la siguiente (en un for, ejecuta antes la actualización).
public class BreakContinue {
    public static void main(String[] args) {
        // break: buscar el primer múltiplo de 7 a partir de 50
        int encontrado = -1;
        for (int i = 50; i <= 100; i++) {
            if (i % 7 == 0) {
                encontrado = i;
                break;                  // sale del bucle inmediatamente
            }
        }
        System.out.println("Primer múltiplo de 7 desde 50: " + encontrado);

        // continue: saltar los múltiplos de 3
        for (int i = 1; i <= 15; i++) {
            if (i % 3 == 0) {
                continue;               // pasa a la siguiente vuelta
            }
            System.out.print(i + " ");
        }
        System.out.println();
    }
}
Primer múltiplo de 7 desde 50: 56
1 2 4 5 7 8 10 11 13 14 

8.1. Etiquetas (desaconsejadas)

break y continue solo afectan al bucle más interno. Si quieres salir de varios bucles anidados de golpe, Java permite poner una etiqueta delante del bucle externo:

public class Etiquetas {
    public static void main(String[] args) {
        // Buscar dos números entre 1 y 9 cuyo producto sea 42
        externo:
        for (int a = 1; a <= 9; a++) {
            for (int b = 1; b <= 9; b++) {
                if (a * b == 42) {
                    System.out.println(a + " x " + b + " = 42");
                    break externo;      // sale de LOS DOS bucles
                }
            }
        }
        System.out.println("Fin de la búsqueda");
    }
}
6 x 7 = 42
Fin de la búsqueda

⚠️ Importante: Las etiquetas existen, y debes reconocerlas si las ves, pero desaconsejamos usarlas: hacen que el flujo del programa salte de forma difícil de seguir (recuerdan al antiguo goto). Casi siempre hay una alternativa más clara: una bandera en la condición de los bucles o, cuando sepas usar métodos (módulo 07), meter los bucles en un método y salir con return. Lo mismo vale, en menor medida, para break y continue: úsalos cuando simplifiquen el código, no por costumbre.


9. Bucles infinitos

Un bucle infinito es el que no termina nunca porque su condición no llega a ser falsa. Causas típicas:

Causa Ejemplo Solución
Olvidar la actualización while (i < 10) { System.out.println(i); } Añade i++
Actualizar en sentido contrario for (int i = 0; i < 10; i--) i++
; después del while while (i < 10); { i++; } Quita el ;
Condición que nunca cambia while (n != 0) sin volver a leer n Lee n al final del cuerpo
Comparar double con != for (double x = 0; x != 1.0; x += 0.1) Usa < o <=, o un contador entero

Los dos últimos son los más traicioneros. Mira lo que pasa con el ; después de un for y con un double que nunca vale exactamente 1.0:

public class ErroresBucles {
    public static void main(String[] args) {
        // Error 1: punto y coma tras el for -> el cuerpo se ejecuta UNA vez
        int i;
        for (i = 0; i < 3; i++); {
            System.out.println("Vuelta con i = " + i);
        }

        // Error 2: comparar double con != (sin el límite de 12 vueltas, sería infinito)
        int vueltas = 0;
        for (double x = 0; x != 1.0 && vueltas < 12; x += 0.1) {
            System.out.print(x + "  ");
            vueltas++;
        }
        System.out.println();
    }
}
Vuelta con i = 3
0.0  0.1  0.2  0.30000000000000004  0.4  0.5  0.6  0.7  0.7999999999999999  0.8999999999999999  0.9999999999999999  1.0999999999999999  

El primer bucle da sus tres vueltas sin hacer nada (su cuerpo es el ;) y luego el bloque se ejecuta una sola vez, con i ya valiendo 3. En el segundo, por los errores de redondeo de los double (módulo 03), x pasa de 0.9999999999999999 a 1.0999999999999999 sin tocar nunca el 1.0. Sin la condición de seguridad vueltas < 12, no pararía jamás.

💡 Consejo: Si tu programa se queda «colgado» en un bucle infinito, en IntelliJ IDEA pulsa el botón rojo Stop de la ventana de ejecución (o Ctrl+F2); en un terminal, Ctrl+C.

9.1. Bucles infinitos controlados: while (true)

A veces se escribe un bucle infinito a propósito y se sale con break cuando se cumple una condición que es más natural comprobar en mitad del cuerpo:

import java.util.Scanner;

public class Comandos {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        while (true) {                       // bucle infinito controlado
            System.out.print("> ");
            String comando = sc.nextLine();
            if (comando.equals("salir")) {
                break;                       // única salida del bucle
            }
            System.out.println("Has escrito: " + comando);
        }
        System.out.println("Adiós");
    }
}
> hola
Has escrito: hola
> qué tal
Has escrito: qué tal
> salir
Adiós

Es un patrón legítimo, pero asegúrate de que siempre existe un camino hacia el break. El compilador, además, detecta el código que hay después de un while (true) sin break, porque nunca podrá ejecutarse:

public class Inalcanzable {
    public static void main(String[] args) {
        while (true) {
            System.out.println("Sin fin");
        }
        System.out.println("Esto nunca se ejecuta");
    }
}
Inalcanzable.java:6: error: unreachable statement
        System.out.println("Esto nunca se ejecuta");
        ^
1 error

10. Bucles anidados

Un bucle puede contener otro bucle. El interno da todas sus vueltas por cada vuelta del externo. Si el externo da n vueltas y el interno m, el cuerpo del interno se ejecuta n × m veces.

Cuadrícula de 3 filas por 4 columnas que muestra el orden en que se ejecutan dos bucles anidados

Figura 4. Ejecución de dos bucles anidados: el interno se reinicia en cada vuelta del externo.

public class OrdenAnidado {
    public static void main(String[] args) {
        int paso = 1;
        for (int i = 1; i <= 3; i++) {           // bucle externo
            for (int j = 1; j <= 4; j++) {       // bucle interno
                System.out.print("(" + i + "," + j + ")#" + paso + "  ");
                paso++;
            }
            System.out.println();                // al acabar cada fila
        }
    }
}
(1,1)#1  (1,2)#2  (1,3)#3  (1,4)#4  
(2,1)#5  (2,2)#6  (2,3)#7  (2,4)#8  
(3,1)#9  (3,2)#10  (3,3)#11  (3,4)#12  

10.1. Tablas

El ejemplo clásico es la tabla de multiplicar completa (tabla pitagórica). El bucle externo recorre las filas y el interno, las columnas de cada fila:

public class TablaPitagoras {
    public static void main(String[] args) {
        // Cabecera
        System.out.print("   |");
        for (int j = 1; j <= 9; j++) {
            System.out.printf("%4d", j);
        }
        System.out.println();
        System.out.println("---+" + "-".repeat(36));

        // Filas
        for (int i = 1; i <= 9; i++) {
            System.out.printf("%2d |", i);
            for (int j = 1; j <= 9; j++) {
                System.out.printf("%4d", i * j);
            }
            System.out.println();
        }
    }
}
   |   1   2   3   4   5   6   7   8   9
---+------------------------------------
 1 |   1   2   3   4   5   6   7   8   9
 2 |   2   4   6   8  10  12  14  16  18
 3 |   3   6   9  12  15  18  21  24  27
 4 |   4   8  12  16  20  24  28  32  36
 5 |   5  10  15  20  25  30  35  40  45
 6 |   6  12  18  24  30  36  42  48  54
 7 |   7  14  21  28  35  42  49  56  63
 8 |   8  16  24  32  40  48  56  64  72
 9 |   9  18  27  36  45  54  63  72  81

El println() vacío está fuera del bucle interno pero dentro del externo: se ejecuta una vez al final de cada fila. "-".repeat(36) repite una cadena (lo verás en el módulo 09).

10.2. Figuras con asteriscos

Dibujar figuras es el mejor entrenamiento para los bucles anidados. La clave es encontrar, para cada fila, cuántos caracteres de cada tipo hay que escribir en función del número de fila:

Figura Fila f (de 1 a n)
Rectángulo 2n asteriscos
Triángulo rectángulo f asteriscos
Pirámide n - f espacios y 2f - 1 asteriscos
import java.util.Scanner;

public class Figuras {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("Altura: ");
        int n = sc.nextInt();

        System.out.println("Rectángulo " + n + "x" + (2 * n) + ":");
        for (int fila = 1; fila <= n; fila++) {
            for (int col = 1; col <= 2 * n; col++) {
                System.out.print("*");
            }
            System.out.println();
        }

        System.out.println("Triángulo:");
        for (int fila = 1; fila <= n; fila++) {
            for (int col = 1; col <= fila; col++) {   // la fila k tiene k asteriscos
                System.out.print("*");
            }
            System.out.println();
        }

        System.out.println("Pirámide:");
        for (int fila = 1; fila <= n; fila++) {
            for (int esp = 1; esp <= n - fila; esp++) {      // espacios a la izquierda
                System.out.print(" ");
            }
            for (int ast = 1; ast <= 2 * fila - 1; ast++) {  // 1, 3, 5, 7...
                System.out.print("*");
            }
            System.out.println();
        }
    }
}
Altura: 4
Rectángulo 4x8:
********
********
********
********
Triángulo:
*
**
***
****
Pirámide:
   *
  ***
 *****
*******

💡 Consejo: Antes de programar una figura, dibújala en papel con una cuadrícula y apunta, fila a fila, cuántos espacios y asteriscos lleva. Cuando veas la fórmula, el código sale solo.


11. ¿Qué bucle elijo?

Situación Bucle recomendado Ejemplo
Sabes cuántas vueltas hay que dar for Tabla de multiplicar, recorrer del 1 al n
Repetir mientras se cumpla algo, quizá ninguna vez while Leer hasta un centinela, algoritmo de Euclides
El cuerpo debe ejecutarse al menos una vez do-while Menús, validar una entrada
Salir desde la mitad del cuerpo while (true) + break Intérprete de comandos
Recorrer todos los elementos de un array o colección for-each Módulo 08

Técnicamente, los tres bucles son intercambiables (cualquier for se puede escribir como while y viceversa). Elegir bien no cambia lo que hace el programa, pero sí lo fácil que es de leer.


12. Ejemplos clásicos

12.1. Números primos

Un número es primo si es mayor que 1 y solo es divisible entre 1 y entre sí mismo. Para comprobarlo basta con buscar divisores entre 2 y √n: si n = a · b, uno de los dos factores es como mucho √n. Por eso la condición es d * d <= n (evita calcular la raíz).

import java.util.Scanner;

public class Primos {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("Número: ");
        int n = sc.nextInt();

        // 1) ¿Es primo n? Buscamos un divisor entre 2 y la raíz cuadrada de n
        boolean esPrimo = n >= 2;
        for (int d = 2; d * d <= n && esPrimo; d++) {
            if (n % d == 0) {
                esPrimo = false;
            }
        }
        System.out.println(n + (esPrimo ? " es primo" : " no es primo"));

        // 2) Lista de primos hasta n (bucles anidados)
        System.out.print("Primos hasta " + n + ": ");
        for (int k = 2; k <= n; k++) {
            boolean primo = true;
            for (int d = 2; d * d <= k && primo; d++) {
                if (k % d == 0) {
                    primo = false;
                }
            }
            if (primo) {
                System.out.print(k + " ");
            }
        }
        System.out.println();
    }
}
Número: 97
97 es primo
Primos hasta 97: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 

La bandera esPrimo forma parte de la condición del for: en cuanto encontramos un divisor, el bucle termina sin necesidad de break.

12.2. Factorial

n! = 1 · 2 · 3 · … · n (y 0! = 1). Es un acumulador de producto. Usamos long porque el factorial crece muy deprisa:

import java.util.Scanner;

public class Factorial {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("n: ");
        int n = sc.nextInt();

        long factorial = 1;                 // acumulador de producto: empieza en 1
        for (int i = 2; i <= n; i++) {
            factorial *= i;
        }

        System.out.println(n + "! = " + factorial);
    }
}
n: 20
20! = 2432902008176640000

n: 21
21! = -4249290049419214848

¡21! sale negativo! El valor real (51 090 942 171 709 440 000) no cabe en un long (máximo ≈ 9,2 · 10¹⁸) y se produce un desbordamiento (overflow): Java no avisa, simplemente «da la vuelta». Para números más grandes existe la clase BigInteger, que verás en el curso avanzado.

12.3. Sucesión de Fibonacci

Cada término es la suma de los dos anteriores: 0, 1, 1, 2, 3, 5, 8… Hay que guardar los dos últimos términos y «desplazarlos» en cada vuelta:

import java.util.Scanner;

public class Fibonacci {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("¿Cuántos términos? ");
        int n = sc.nextInt();

        long anterior = 0, actual = 1;
        for (int i = 1; i <= n; i++) {
            System.out.print(anterior + " ");
            long siguiente = anterior + actual;
            anterior = actual;
            actual = siguiente;
        }
        System.out.println();
    }
}
¿Cuántos términos? 15
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 

12.4. Adivinar un número

El ordenador piensa un número al azar y el usuario tiene siete intentos para adivinarlo. Para generar números aleatorios se usa la clase java.util.Random:

Llamada Resultado
random.nextInt(10) Entero entre 0 y 9
random.nextInt(1, 101) Entero entre 1 y 100 (el segundo límite no se incluye; Java 17+)
random.nextInt(100) + 1 Entero entre 1 y 100 (forma clásica)
random.nextDouble() double entre 0.0 (incluido) y 1.0 (excluido)
Math.random() Lo mismo, sin crear un objeto Random
import java.util.Random;
import java.util.Scanner;

public class Adivina {
    public static void main(String[] args) {
        final int MAX_INTENTOS = 7;
        Scanner sc = new Scanner(System.in);
        Random random = new Random();
        int secreto = random.nextInt(1, 101);   // entre 1 y 100 (Java 17+)

        System.out.println("He pensado un número entre 1 y 100.");
        int intentos = 0;
        boolean acertado = false;

        while (!acertado && intentos < MAX_INTENTOS) {
            System.out.print("Intento " + (intentos + 1) + ": ");
            int propuesta = sc.nextInt();
            intentos++;

            if (propuesta == secreto) {
                acertado = true;
            } else if (propuesta < secreto) {
                System.out.println("Más alto.");
            } else {
                System.out.println("Más bajo.");
            }
        }

        if (acertado) {
            System.out.println("¡Correcto! Lo has adivinado en " + intentos + " intentos.");
        } else {
            System.out.println("Has agotado los intentos. Era el " + secreto + ".");
        }
    }
}

Una partida real (en la tuya el número secreto será otro, por eso es un juego):

He pensado un número entre 1 y 100.
Intento 1: 50
Más alto.
Intento 2: 75
Más bajo.
Intento 3: 62
Más alto.
Intento 4: 68
Más bajo.
Intento 5: 65
Más bajo.
Intento 6: 63
¡Correcto! Lo has adivinado en 6 intentos.

💡 Consejo: Si buscas siempre en la mitad del intervalo que queda (50, luego 75 o 25…), nunca necesitarás más de 7 intentos para un número entre 1 y 100, porque 2⁷ = 128 > 100. Es la idea de la búsqueda binaria, que programarás en el módulo 08.

12.5. Las cifras de un número

Con % 10 obtienes la última cifra de un número y con / 10 la eliminas. Repitiendo las dos operaciones recorres todas las cifras de derecha a izquierda:

import java.util.Scanner;

public class Digitos {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("Número entero: ");
        int n = Math.abs(sc.nextInt());   // trabajamos con el valor absoluto

        int cifras = 0, suma = 0, invertido = 0;
        int resto = n;
        do {                                // do-while: el 0 también tiene una cifra
            int ultima = resto % 10;        // última cifra
            cifras++;
            suma += ultima;
            invertido = invertido * 10 + ultima;
            resto /= 10;                    // quitamos la última cifra
        } while (resto > 0);

        System.out.println("Cifras: " + cifras);
        System.out.println("Suma de las cifras: " + suma);
        System.out.println("Invertido: " + invertido);
        System.out.println(n == invertido ? "Es capicúa" : "No es capicúa");
    }
}
Número entero: 40721
Cifras: 5
Suma de las cifras: 14
Invertido: 12704
No es capicúa

Usamos do-while para que el número 0 cuente como una cifra. La línea invertido = invertido * 10 + ultima «empuja» las cifras ya colocadas una posición a la izquierda y añade la nueva a la derecha.

12.6. Números perfectos

Un número es perfecto si es igual a la suma de sus divisores propios (los menores que él): 6 = 1 + 2 + 3. Son muy escasos:

public class Perfectos {
    public static void main(String[] args) {
        // Un número es perfecto si es igual a la suma de sus divisores propios
        System.out.print("Números perfectos hasta 10000: ");
        for (int n = 2; n <= 10000; n++) {
            int sumaDivisores = 1;              // el 1 siempre divide
            for (int d = 2; d <= n / 2; d++) {
                if (n % d == 0) {
                    sumaDivisores += d;
                }
            }
            if (sumaDivisores == n) {
                System.out.print(n + " ");
            }
        }
        System.out.println();
    }
}
Números perfectos hasta 10000: 6 28 496 8128 

Son dos bucles anidados: el externo recorre los candidatos y el interno suma sus divisores. Basta con buscar divisores hasta n / 2, porque ningún divisor propio puede ser mayor.

12.7. MCD y MCM: algoritmo de Euclides

El máximo común divisor se calcula con el algoritmo de Euclides (siglo III a. C.): MCD(a, b) = MCD(b, a mod b), hasta que el segundo número sea 0. El mínimo común múltiplo se obtiene después: MCM(a, b) = a · b / MCD(a, b).

import java.util.Scanner;

public class McdMcm {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("Dos números positivos: ");
        int a = sc.nextInt();
        int b = sc.nextInt();

        // Algoritmo de Euclides
        int x = a, y = b;
        while (y != 0) {
            int resto = x % y;
            x = y;
            y = resto;
        }
        int mcd = x;
        int mcm = a / mcd * b;     // dividir primero evita desbordamientos

        System.out.println("MCD(" + a + ", " + b + ") = " + mcd);
        System.out.println("MCM(" + a + ", " + b + ") = " + mcm);
    }
}
Dos números positivos: 48 18
MCD(48, 18) = 6
MCM(48, 18) = 144

Traza con 48 y 18:

Vuelta x y resto = x % y
1 48 18 12
2 18 12 6
3 12 6 0
fin 6 0 — (y == 0: termina, MCD = 6)

12.8. Menú repetitivo

El esqueleto de casi cualquier aplicación de consola es un do-while que muestra un menú, lee una opción, la procesa con un switch y se repite hasta que el usuario elige salir:

flowchart TD
    A([Inicio]) --> B[/Mostrar menú y leer opción/]
    B --> C{opción}
    C -- 1 --> D[Ingresar]
    C -- 2 --> E[Retirar]
    C -- 3 --> F[/Ver saldo/]
    C -- 0 --> G[/Despedida/]
    C -- otra --> H[/Opción no válida/]
    D & E & F & G & H --> I{"opción != 0"}
    I -- Sí --> B
    I -- No --> J([Fin])
import java.util.Scanner;

public class Cajero {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        double saldo = 0;
        int opcion;

        do {
            System.out.println();
            System.out.println("1. Ingresar  2. Retirar  3. Ver saldo  0. Salir");
            System.out.print("Opción: ");
            opcion = sc.nextInt();

            switch (opcion) {
                case 1 -> {
                    System.out.print("Cantidad a ingresar: ");
                    double cantidad = sc.nextDouble();
                    if (cantidad > 0) {
                        saldo += cantidad;
                    } else {
                        System.out.println("Cantidad no válida.");
                    }
                }
                case 2 -> {
                    System.out.print("Cantidad a retirar: ");
                    double cantidad = sc.nextDouble();
                    if (cantidad > 0 && cantidad <= saldo) {
                        saldo -= cantidad;
                    } else {
                        System.out.println("Operación denegada.");
                    }
                }
                case 3 -> System.out.printf("Saldo: %.2f €%n", saldo);
                case 0 -> System.out.println("¡Hasta pronto!");
                default -> System.out.println("Opción no válida.");
            }
        } while (opcion != 0);
    }
}

1. Ingresar  2. Retirar  3. Ver saldo  0. Salir
Opción: 1
Cantidad a ingresar: 100

1. Ingresar  2. Retirar  3. Ver saldo  0. Salir
Opción: 2
Cantidad a retirar: 30

1. Ingresar  2. Retirar  3. Ver saldo  0. Salir
Opción: 2
Cantidad a retirar: 500
Operación denegada.

1. Ingresar  2. Retirar  3. Ver saldo  0. Salir
Opción: 3
Saldo: 70.00 €

1. Ingresar  2. Retirar  3. Ver saldo  0. Salir
Opción: 0
¡Hasta pronto!

En el módulo 07 aprenderás a sacar cada opción a su propio método, para que el switch quede limpio y cada operación se pueda probar por separado.


13. Ejercicios

Los seis primeros están resueltos; intenta hacerlos antes de mirar la solución.

Ejercicio 1: Suma de los pares

Lee un número N y calcula la suma de todos los números pares entre 1 y N.

Ver solución
import java.util.Scanner;

public class SumaPares {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("N: ");
        int n = sc.nextInt();

        int suma = 0;
        for (int i = 2; i <= n; i += 2) {   // solo recorremos los pares
            suma += i;
        }
        System.out.println("Suma de los pares hasta " + n + ": " + suma);
    }
}
N: 10
Suma de los pares hasta 10: 30

En lugar de recorrer todos los números y comprobar con if (i % 2 == 0), empezamos en 2 y avanzamos de 2 en 2: el bucle da la mitad de vueltas y no necesita condicional. Comprobación: 2 + 4 + 6 + 8 + 10 = 30.

Ejercicio 2: Potencia sin Math.pow

Lee una base y un exponente entero no negativo y calcula la potencia con un bucle.

Ver solución
import java.util.Scanner;

public class Potencia {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("Base y exponente (entero >= 0): ");
        int base = sc.nextInt();
        int exponente = sc.nextInt();

        if (exponente < 0) {
            System.out.println("El exponente debe ser mayor o igual que 0.");
        } else {
            long resultado = 1;
            for (int i = 1; i <= exponente; i++) {
                resultado *= base;
            }
            System.out.println(base + "^" + exponente + " = " + resultado);
        }
    }
}
Base y exponente (entero >= 0): 2 10
2^10 = 1024

Es un acumulador de producto que empieza en 1. Con exponente 0 el bucle no da ninguna vuelta y el resultado es 1, que es correcto (a⁰ = 1).

Ejercicio 3: Triángulo de números

Lee un número de filas y dibuja un triángulo en el que la fila k contiene los números del 1 al k.

Ver solución
import java.util.Scanner;

public class TrianguloNumeros {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("Filas: ");
        int n = sc.nextInt();

        for (int fila = 1; fila <= n; fila++) {
            for (int k = 1; k <= fila; k++) {
                System.out.print(k + " ");
            }
            System.out.println();
        }
    }
}
Filas: 5
1 
1 2 
1 2 3 
1 2 3 4 
1 2 3 4 5 

Es el triángulo de asteriscos de la sección 10.2 escribiendo k en lugar de *. El límite del bucle interno (k <= fila) depende de la variable del externo: eso es lo que hace que cada fila sea más larga.

Ejercicio 4: Conjetura de Collatz

Partiendo de un número n > 0: si es par, divídelo entre 2; si es impar, multiplícalo por 3 y súmale 1. La conjetura de Collatz dice que, empieces por donde empieces, siempre acabas llegando a 1 (nadie lo ha demostrado todavía). Cuenta cuántos pasos se necesitan y cuál es el valor máximo que se alcanza.

Ver solución
import java.util.Scanner;

public class Collatz {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("Número inicial (> 0): ");
        long n = sc.nextLong();

        int pasos = 0;
        long maximo = n;
        while (n != 1) {
            if (n % 2 == 0) {
                n = n / 2;
            } else {
                n = 3 * n + 1;
            }
            pasos++;
            if (n > maximo) {
                maximo = n;
            }
        }
        System.out.println("Llega a 1 en " + pasos + " pasos.");
        System.out.println("Valor máximo alcanzado: " + maximo);
    }
}
Número inicial (> 0): 27
Llega a 1 en 111 pasos.
Valor máximo alcanzado: 9232

No sabemos cuántas vueltas habrá, así que usamos while. El programa combina un contador (pasos) y un máximo (maximo, inicializado con el propio n). Usamos long porque los valores intermedios pueden crecer mucho: empezando en 27 se llega a 9232.

Ejercicio 5: PIN con tres intentos

Pide un PIN numérico (el correcto es 1234). El usuario tiene tres intentos. Si acierta, muestra «Acceso concedido»; si falla los tres, «Tarjeta bloqueada». Tras cada fallo, indica cuántos intentos le quedan.

Ver solución
import java.util.Scanner;

public class Pin {
    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;
        boolean correcto = false;
        do {
            System.out.print("PIN: ");
            int pin = sc.nextInt();
            intentos++;
            if (pin == PIN_CORRECTO) {
                correcto = true;
            } else if (intentos < MAX_INTENTOS) {
                System.out.println("PIN incorrecto. Intentos restantes: " + (MAX_INTENTOS - intentos));
            }
        } while (!correcto && intentos < MAX_INTENTOS);

        System.out.println(correcto ? "Acceso concedido." : "Tarjeta bloqueada.");
    }
}
PIN: 1111
PIN incorrecto. Intentos restantes: 2
PIN: 2222
PIN incorrecto. Intentos restantes: 1
PIN: 1234
Acceso concedido.

PIN: 1111
PIN incorrecto. Intentos restantes: 2
PIN: 2222
PIN incorrecto. Intentos restantes: 1
PIN: 3333
Tarjeta bloqueada.

El bucle tiene dos formas de terminar (acertar o agotar los intentos), así que la condición combina la bandera y el contador con &&. Al salir, la bandera nos dice cuál de las dos ocurrió. El else if (intentos < MAX_INTENTOS) evita decir «Intentos restantes: 0» antes de bloquear.

Ejercicio 6: Rombo de asteriscos

Lee la altura n de la mitad superior y dibuja un rombo de 2n − 1 filas.

Ver solución
import java.util.Scanner;

public class Rombo {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("Altura de la mitad superior: ");
        int n = sc.nextInt();

        // El rombo tiene 2n - 1 filas; la "anchura" k sube hasta n y luego baja
        for (int fila = 1; fila <= 2 * n - 1; fila++) {
            int k = (fila <= n) ? fila : 2 * n - fila;
            for (int esp = 1; esp <= n - k; esp++) {
                System.out.print(" ");
            }
            for (int ast = 1; ast <= 2 * k - 1; ast++) {
                System.out.print("*");
            }
            System.out.println();
        }
    }
}
Altura de la mitad superior: 4
   *
  ***
 *****
*******
 *****
  ***
   *

En lugar de escribir dos bloques de bucles (mitad superior e inferior), calculamos para cada fila una «anchura» k que sube de 1 a n y luego baja. Con ella aplicamos la fórmula de la pirámide: n - k espacios y 2k - 1 asteriscos.

Ejercicio 7: Divisores (propuesto)

Lee un número entero positivo y muestra todos sus divisores, cuántos tiene y su suma. Por ejemplo, 12 tiene 6 divisores (1 2 3 4 6 12) que suman 28.

Ejercicio 8: De decimal a binario (propuesto)

Convierte un número entero positivo a binario sin usar Integer.toBinaryString. Pista: divide sucesivamente entre 2 y quédate con los restos; el binario son los restos leídos al revés (puedes construirlo como un número, igual que invertido en la sección 12.5, o como una cadena concatenando delante).

Ejercicio 9: Estadísticas con centinela (propuesto)

Lee números enteros hasta que se introduzca el 0. Al final muestra cuántos positivos y cuántos negativos se han introducido, la media de los positivos y el menor de todos los números (sin contar el 0). Comprueba que el programa no falla si el primer número es 0.

Ejercicio 10: Aproximación de π (propuesto)

La serie de Leibniz dice que π / 4 = 1 − 1/3 + 1/5 − 1/7 + 1/9 − … Calcula π sumando los N primeros términos (lee N) y muestra el valor obtenido con 10 decimales y la diferencia con Math.PI. Prueba con 10, 1000 y 1 000 000 términos.

Ejercicio 11: El ordenador adivina (propuesto)

Invierte el juego de la sección 12.4: el usuario piensa un número entre 1 y 100 y el ordenador intenta adivinarlo. En cada intento, el ordenador propone el valor central del intervalo posible y el usuario responde + (más alto), - (más bajo) o = (acertado). Muestra cuántos intentos ha necesitado. ¿Puede necesitar más de 7?

Ejercicio 12: Números de Armstrong (propuesto)

Un número de tres cifras es de Armstrong si es igual a la suma de los cubos de sus cifras (por ejemplo, 153 = 1³ + 5³ + 3³). Muéstralos todos. Hazlo de dos formas: con tres bucles anidados (uno por cifra) y con un único bucle que separe las cifras con % y /.


Resumen

Concepto Sintaxis / idea clave
Partes de un bucle ① inicialización, ② condición, ③ cuerpo, ④ actualización
while while (cond) { ... }: 0 o más vueltas
do-while do { ... } while (cond);: 1 o más vueltas, termina en ;
for for (int i = 0; i < n; i++) { ... }: número de vueltas conocido
Centinela Leer antes del bucle y al final del cuerpo; el centinela no se procesa
Contador int c = 0; … c++;
Acumulador suma = 0 y suma += x; producto = 1 y producto *= x
Máximo/mínimo Inicializar con el primer dato; comparar en cada vuelta
Bandera boolean encontrado = false; que pasa a true cuando ocurre algo
break / continue Salir del bucle / saltar a la siguiente vuelta
Etiquetas externo: for (...) + break externo; (desaconsejadas)
Bucle infinito Falta de actualización, ; tras el while, double con !=
Anidados El interno da todas sus vueltas por cada vuelta del externo (n × m)
Aleatorios new Random().nextInt(1, 101) (Java 17+) o Math.random()
Cifras de un número n % 10 (última cifra) y n / 10 (quitarla)