Control de flujo en Java — ejercicios con la nota de acceso, la biblioteca y un cajero de tres intentos (y el 9,5 que salía «Suficiente»)

Un compañero de clase me enseñó una vez su validador de notas, muy orgulloso: «funciona, lo he probado». Lo había probado con un 6. Le pedí que metiera un 9,5 y el programa contestó, sin despeinarse: «Suficiente». Ni error, ni excepción, ni aviso del compilador. Las condiciones estaban bien escritas, una por una; lo que estaba mal era el orden: la primera era nota >= 5, y un 9,5 también es mayor o igual que 5. En una cadena de else if la primera condición verdadera gana y las demás ni se miran. Ese fallo lo vas a ver con su salida real en «¿Qué falla aquí?», y el primer ejercicio te obliga a ordenarlas bien.

Aquí tienes tres ejercicios de control de flujo en Java, de menos a más: la nota de acceso a la universidad (rangos con else if y validar antes de calcular), el préstamo de la biblioteca de la facultad (switch con String y cada caso con sus llaves) y un cajero con tres intentos para el PIN sin bucles y una operación con switch. Como en la práctica de control de flujo, no hay ni bucles ni arrays: cada ejecución hace una sola cosa. Al final tienes el cuestionario tipo examen y el chuletario.

Cómo usar este artículo

  1. Lee el enunciado y la salida esperada de cada ejercicio. La salida es real: si tu programa imprime lo mismo con los mismos datos, vas bien.
  2. Inténtalo sin mirar nada al menos 15 minutos. Si te atascas, abre la pista.
  3. Antes de las soluciones, haz «¿Qué falla aquí?»: tres fragmentos con un error cada uno. Predice qué imprimen antes de leer la explicación.
  4. Compara tu solución con la comentada. No tiene que ser igual: tiene que dar la misma salida con los mismos datos y por las mismas razones.

Las salidas son de una única ejecución con Java 21 en un equipo en español, así que los decimales se teclean y se imprimen con coma (11,8). Lo que aparece detrás de cada pregunta es lo que se tecleó. En el entorno de Java de la web lo tecleado va en la pestaña Entrada, una línea por dato.

Nivel básico

Ejercicio 1 — Validador de nota de acceso

Escribe ValidadorNota: pide una nota de acceso a la universidad (de 0 a 14, como en la EBAU con la fase específica) y la clasifica así:

NotaClasificación
menos de 5No apto
de 5 a menos de 7Suficiente
de 7 a menos de 9Notable
de 9 a menos de 11Muy bueno
11 o másExcelente

Después dice cuánto le sobra o le falta para la nota de corte de GCID, que tomaremos como 10,5 (inventada), con dos decimales. Si la nota está fuera de 0-14, avisa y no calcula nada más. Por último imprime el margen sin redondear con println, para ver qué guarda de verdad el double.

Salida esperada con 11,8:

=== VALIDADOR DE NOTA DE ACCESO ===
Nota de acceso (0-14): 11,8

--- Resultado ---
Clasificación: Excelente
Entras en GCID: 1,30 por encima del corte (10,5).
Margen sin redondear: 1.3000000000000007

Con 15:

=== VALIDADOR DE NOTA DE ACCESO ===
Nota de acceso (0-14): 15
Dato no válido: la nota de acceso va de 0 a 14.

Con 4,99:

=== VALIDADOR DE NOTA DE ACCESO ===
Nota de acceso (0-14): 4,99

--- Resultado ---
Clasificación: No apto
Te faltan 5,51 para el corte (10,5).
Margen sin redondear: -5.51

Valida el rango con un if al principio y corta con return; si falla. Para la clasificación, una cadena else if que empiece por el tramo más alto (nota >= 11): así cada condición solo necesita el límite de abajo. El margen es nota - CORTE: si es negativo, imprime -margen en el mensaje de lo que falta. ¿Por qué 1,30 con printf y otra cosa con println? Lo verás en la solución.

Nivel intermedio

Ejercicio 2 — Menú de biblioteca

Escribe Biblioteca: la biblioteca de la facultad atiende una operación por ejecución, elegida en un menú.

  • 1. Pedir un préstamo: pide el tipo de socio, cuántos libros tiene ya y el título. Según el tipo: estudiante 15 días y 3 libros como mucho; PDI 30 días y 6 libros; visitante 7 días y 1 libro. Si el tipo no existe, si falta el título o si ya tiene el máximo, lo dice; si no, confirma el préstamo.
  • 2. Devolver un libro: pide el tipo de socio y los días de retraso. A tiempo, lo agradece; el PDI no paga multa; los demás pagan 0,20 € por día con un máximo de 5 €.
  • 3. Ver las normas.
  • Cualquier otro número: «Opción no válida».

El tipo de socio se acepta con cualquier combinación de mayúsculas (PDI, pdi). Las normas son inventadas.

Salida esperada de un préstamo y de un préstamo que no cabe:

=== BIBLIOTECA DE LA FACULTAD ===
1. Pedir un préstamo
2. Devolver un libro
3. Ver las normas
Opción: 1
Tipo de socio (estudiante/pdi/visitante): estudiante
Libros que ya tienes: 1
Título: Clean Code
Préstamo hecho: «Clean Code» durante 15 días (2 de 3 libros).
=== BIBLIOTECA DE LA FACULTAD ===
1. Pedir un préstamo
2. Devolver un libro
3. Ver las normas
Opción: 1
Tipo de socio (estudiante/pdi/visitante): visitante
Libros que ya tienes: 1
Título: Java a fondo
No puedes llevarte más: ya tienes 1 de 1.

Devoluciones con 9 días de retraso, con 40 (llega al máximo) y de un PDI escrito en mayúsculas:

=== BIBLIOTECA DE LA FACULTAD ===
1. Pedir un préstamo
2. Devolver un libro
3. Ver las normas
Opción: 2
Tipo de socio (estudiante/pdi/visitante): estudiante
Días de retraso: 9
Multa: 1,80 € (0,20 € por día, como mucho 5 €).
=== BIBLIOTECA DE LA FACULTAD ===
1. Pedir un préstamo
2. Devolver un libro
3. Ver las normas
Opción: 2
Tipo de socio (estudiante/pdi/visitante): estudiante
Días de retraso: 40
Multa: 5,00 € (0,20 € por día, como mucho 5 €).
=== BIBLIOTECA DE LA FACULTAD ===
1. Pedir un préstamo
2. Devolver un libro
3. Ver las normas
Opción: 2
Tipo de socio (estudiante/pdi/visitante): PDI
Días de retraso: 12
Devuelto con 12 días de retraso: el PDI no paga multa.

Y una opción que no existe:

=== BIBLIOTECA DE LA FACULTAD ===
1. Pedir un préstamo
2. Devolver un libro
3. Ver las normas
Opción: 5
Opción no válida: 5

El menú es un switch (opcion). Dentro del caso 1, otro switch (tipo) con String que dé valor a dias y maximo (con su break en cada caso, o con flechas). Si en los casos 1 y 2 declaras una variable con el mismo nombre (tipo), pon cada caso entre llaves propias: case 1: { … break; }. Lee el tipo con trim().toLowerCase() para no depender de las mayúsculas. La multa: retraso * 0.20 y, si pasa de 5, se queda en 5.

Desafío final

Ejercicio 3 — Cajero automático con PIN

Escribe CajeroPIN: el PIN correcto es 4321 y hay tres intentos sin usar bucles. Tras cada fallo dice cuántos intentos quedan; al tercer fallo, «Tarjeta bloqueada. Llama a tu banco.» y el programa termina. Con el PIN bien, un menú con una operación:

  • 1. Consultar saldo (empieza en 500,00 €).
  • 2. Sacar dinero: la cantidad tiene que ser positiva y múltiplo de 10, como mucho 600 € por operación, y no más que el saldo. Comprueba en ese orden.
  • 3. Ingresar dinero: de 1 a 1000 € por ingreso.

Lee siempre líneas (nextLine() + Integer.parseInt), y guarda el PIN como texto. Salida esperada acertando a la segunda y sacando 120 €:

=== CAJERO AUTOMÁTICO ===
PIN: 1111
PIN incorrecto (2 intentos restantes)
PIN: 4321
PIN correcto.

1. Consultar saldo
2. Sacar dinero
3. Ingresar dinero
Operación: 2
Cantidad (múltiplo de 10): 120
Retira 120 €. Saldo: 380,00 €

Tres fallos:

=== CAJERO AUTOMÁTICO ===
PIN: 1111
PIN incorrecto (2 intentos restantes)
PIN: 2222
PIN incorrecto (1 intento restante)
PIN: 0000
Tarjeta bloqueada. Llama a tu banco.

Sin saldo suficiente, con una cantidad que no se puede dar en billetes y con un ingreso:

=== CAJERO AUTOMÁTICO ===
PIN: 4321
PIN correcto.

1. Consultar saldo
2. Sacar dinero
3. Ingresar dinero
Operación: 2
Cantidad (múltiplo de 10): 550
Saldo insuficiente: tienes 500,00 €.
=== CAJERO AUTOMÁTICO ===
PIN: 4321
PIN correcto.

1. Consultar saldo
2. Sacar dinero
3. Ingresar dinero
Operación: 2
Cantidad (múltiplo de 10): 125
Cantidad no válida: el cajero solo da billetes de 10, 20 y 50.
=== CAJERO AUTOMÁTICO ===
PIN: 4321
PIN correcto.

1. Consultar saldo
2. Sacar dinero
3. Ingresar dinero
Operación: 3
Cantidad a ingresar: 200
Ingreso hecho. Saldo: 700,00 €

Sin bucles, los tres intentos son tres if (!pin.equals(PIN)), cada uno dentro del anterior: solo se pide el segundo PIN si el primero falló. En el más profundo, bloqueo y return;. Si llegas a la línea de después del primer if, es que algún intento acertó. Para el plural, intentos vale 2 tras el primer fallo y 1 tras el segundo. ¿Por qué "4321" y no 4321? Piensa en un PIN como 0420.

¿Qué falla aquí?

Antes de las soluciones, tres fragmentos con un error cada uno: los tres compilan sin avisos. Lee cada uno y predice qué imprime antes de mirar la salida.

Error 1

public class OrdenElseIf {
    public static void main(String[] args) {
        double nota = 9.5;
        String clase;
        if (nota >= 5) {
            clase = "Suficiente";
        } else if (nota >= 7) {
            clase = "Notable";
        } else if (nota >= 9) {
            clase = "Muy bueno";
        } else {
            clase = "No apto";
        }
        System.out.println(nota + " → " + clase);
    }
}

¿Qué imprime con nota = 9.5? Salida real:

9.5 → Suficiente

9.5 >= 5 es verdadero, así que entra en la primera rama y la cadena termina ahí: los else if de después no se evalúan. El dibujo lo enseña sobre la recta de notas:

La primera condición verdadera ganaRecta de notas de 0 a 14. Si la primera condición es nota >= 5, todo lo que va de 5 a 14 se queda en Suficiente y el 9,5 nunca llega a Muy bueno. De mayor a menor, cada tramo tiene su nombre y el 9,5 cae en Muy bueno.Orden equivocado: if (nota >= 5) primeroNo aptoSuficiente: se lo come todoOrden bueno: de mayor a menorNo aptoSuficienteNotableMuy buenoExcelente05791114nota = 9,5
Con el orden al revés, el primer if atrapa todo lo que está por encima de 5 y los demás no llegan a mirarse.

Con nota >= 5 arriba, todo lo que va de 5 a 14 se queda en «Suficiente»: las ramas de «Notable» y «Muy bueno» son código muerto.

Ordenar los rangos de menor a mayor usando solo el límite de abajo. Cada condición es correcta por separado, pero la primera se lo come todo. Fíjate en el 9.5 de la salida: lo imprime + con un double, que usa siempre punto.

Arreglo: de mayor a menor.

public class OrdenElseIf {
    public static void main(String[] args) {
        double nota = 9.5;
        String clase;
        if (nota >= 9) {                   // de mayor a menor
            clase = "Muy bueno";
        } else if (nota >= 7) {
            clase = "Notable";
        } else if (nota >= 5) {
            clase = "Suficiente";
        } else {
            clase = "No apto";
        }
        System.out.println(nota + " → " + clase);
    }
}
9.5 → Muy bueno

Si prefieres ir de menor a mayor, cada condición necesita sus dos límites: nota >= 5 && nota < 7. Funciona, pero es más largo y si te equivocas en un límite aparecen huecos (¿qué pasa con un 6,99 si escribes nota <= 6.9?). De mayor a menor no hay huecos posibles.

Error 2

public class DiasPrestamo {
    public static void main(String[] args) {
        String tipo = "estudiante";
        int dias = 0;
        switch (tipo) {
            case "estudiante":
                dias = 15;
            case "pdi":
                dias = 30;
            case "visitante":
                dias = 7;
        }
        System.out.println(tipo + ": " + dias + " días");
    }
}

¿Cuántos días le da a un estudiante? Salida real:

estudiante: 7 días

Falta el break en cada caso. Java entra en case "estudiante", pone dias = 15, cae en case "pdi" y lo cambia a 30, cae en case "visitante" y lo deja en 7. El último valor es el que se queda. Es el mismo fall-through que en la práctica sumaba plazos a propósito; aquí destroza el resultado.

Pensar que el switch sale solo al terminar un case. En el switch clásico solo sale con break (o al llegar al final). Sin él, todos los casos de debajo se ejecutan.

Arreglo: el switch con flechas, que además devuelve el valor.

public class DiasPrestamo {
    public static void main(String[] args) {
        String tipo = "estudiante";
        int dias = switch (tipo) {         // con flechas no hay fall-through
            case "estudiante" -> 15;
            case "pdi" -> 30;
            case "visitante" -> 7;
            default -> 0;                  // en un switch que devuelve valor, default es obligatorio
        };
        System.out.println(tipo + ": " + dias + " días");
    }
}
estudiante: 15 días

Error 3

public class EdadValida {
    public static void main(String[] args) {
        int edad = 150;
        if (edad < 16 && edad > 99) {
            System.out.println("Edad fuera de rango");
        } else {
            System.out.println("Edad correcta: " + edad);
        }
    }
}

¿Rechaza los 150 años? Salida real:

Edad correcta: 150

edad < 16 && edad > 99 pide que la edad sea a la vez menor que 16 y mayor que 99: ningún número cumple las dos cosas, así que la condición es siempre falsa y todo cae en el else. Con 150: 150 < 16 es false, y con && Java ni siquiera mira la segunda parte (cortocircuito).

Traducir «fuera de rango» con &&. «Fuera» significa «por debajo o por encima»: ||. Si lo piensas en positivo («dentro» = edad >= 16 && edad <= 99), lo de fuera es su negación: !(edad >= 16 && edad <= 99), que por las leyes de De Morgan es edad < 16 || edad > 99.

Arreglo:

public class EdadValida {
    public static void main(String[] args) {
        int edad = 150;
        if (edad < 16 || edad > 99) {      // basta con que falle UNA de las dos
            System.out.println("Edad fuera de rango");
        } else {
            System.out.println("Edad correcta: " + edad);
        }
    }
}
Edad fuera de rango

Soluciones comentadas

Solución Ejercicio 1

import java.util.Scanner;

/**
 * Nota de acceso a la universidad (0-14): validar el rango ANTES de calcular y clasificar
 * con else if de mayor a menor. Nota de corte inventada.
 */
public class ValidadorNota {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        final double CORTE = 10.5;                 // nota de corte de GCID (inventada)

        System.out.println("=== VALIDADOR DE NOTA DE ACCESO ===");
        System.out.print("Nota de acceso (0-14): ");
        double nota = sc.nextDouble();             // equipo en español: se teclea con coma (11,8)

        // 1. Validar primero: si el dato no tiene sentido, se corta aquí y no se calcula nada más
        if (nota < 0 || nota > 14) {
            System.out.println("Dato no válido: la nota de acceso va de 0 a 14.");
            return;
        }

        // 2. Clasificar de MAYOR a menor: la primera condición verdadera gana y las demás ni se miran.
        //    Por eso basta con «nota >= 9» y no hace falta «nota >= 9 && nota < 11».
        String clase;
        if (nota >= 11) {
            clase = "Excelente";
        } else if (nota >= 9) {
            clase = "Muy bueno";
        } else if (nota >= 7) {
            clase = "Notable";
        } else if (nota >= 5) {
            clase = "Suficiente";
        } else {
            clase = "No apto";
        }
        System.out.println();
        System.out.println("--- Resultado ---");
        System.out.println("Clasificación: " + clase);

        // 3. Margen sobre el corte: positivo si entra, negativo si le falta
        double margen = nota - CORTE;
        if (margen >= 0) {
            System.out.printf("Entras en GCID: %.2f por encima del corte (%.1f).%n", margen, CORTE);
        } else {
            System.out.printf("Te faltan %.2f para el corte (%.1f).%n", -margen, CORTE);
        }
        // println no usa el idioma del equipo: punto decimal y todos los decimales que tenga el double
        System.out.println("Margen sin redondear: " + margen);
        sc.close();
    }
}

Por qué está hecho así:

  • Validar primero y cortar con return. Si el 15 siguiera adelante, saldría «Excelente» con 4,50 de margen: un resultado que parece correcto con un dato imposible. Con el return al principio, todo lo que viene después puede dar por hecho que la nota está entre 0 y 14.
  • De mayor a menor. Con 11,8 la primera condición (11.8 >= 11) ya es verdadera: «Excelente». Con 4,99 fallan las cuatro y llega al else: «No apto». Ningún valor entre 0 y 14 se queda sin clase.
  • String clase; sin valor inicial. Java comprueba que todas las ramas le dan un valor antes de usarla. Si quitas el else final, javac se queja: variable clase might not have been initialized. Es una ayuda: te avisa de que hay notas sin clasificar.
  • El margen con printf y con println. 11,8 − 10,5 debería ser 1,3, pero en binario 11,8 no es exacto y el double guarda 1.3000000000000007. printf("%.2f") redondea a dos decimales y usa la coma del equipo (1,30); println enseña el número tal cual, con punto, porque no tiene en cuenta el idioma. Con 4,99 la cuenta da −5,51 y aquí sí sale limpia. Por eso, para enseñar resultados, printf.
  • -margen en el mensaje de lo que falta. «Te faltan -5,51» se lee raro; con el signo cambiado, «Te faltan 5,51».
Nota tecleada¿Rango?Primera condición verdaderaClaseMargen
11,8Sínota >= 11Excelente11,8 − 10,5 = 1,30
15No: 15 > 14(no llega)(no llega)(no llega)
4,99Síninguna: elseNo apto4,99 − 10,5 = −5,51

Solución Ejercicio 2

import java.util.Scanner;

/**
 * Biblioteca de la facultad: una operación por ejecución (préstamo, devolución o normas), sin arrays.
 * Plazos, límites y multas inventados.
 */
public class Biblioteca {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.println("=== BIBLIOTECA DE LA FACULTAD ===");
        System.out.println("1. Pedir un préstamo");
        System.out.println("2. Devolver un libro");
        System.out.println("3. Ver las normas");
        System.out.print("Opción: ");
        int opcion = sc.nextInt();
        sc.nextLine();                             // fuera el Intro antes de leer textos

        switch (opcion) {
            case 1: {                              // llaves propias: «tipo» también se declara en el caso 2
                System.out.print("Tipo de socio (estudiante/pdi/visitante): ");
                String tipo = sc.nextLine().trim().toLowerCase();
                System.out.print("Libros que ya tienes: ");
                int tiene = sc.nextInt();
                sc.nextLine();
                System.out.print("Título: ");
                String titulo = sc.nextLine().trim();

                int dias;                          // días de préstamo
                int maximo;                        // libros a la vez
                switch (tipo) {                    // switch con String: compara con equals(), no con ==
                    case "estudiante":
                        dias = 15;
                        maximo = 3;
                        break;
                    case "pdi":
                        dias = 30;
                        maximo = 6;
                        break;
                    case "visitante":
                        dias = 7;
                        maximo = 1;
                        break;
                    default:
                        dias = 0;                  // marca de «tipo desconocido»
                        maximo = 0;
                }

                if (dias == 0) {
                    System.out.println("Tipo de socio desconocido: " + tipo);
                } else if (titulo.isEmpty()) {
                    System.out.println("Falta el título.");
                } else if (tiene >= maximo) {
                    System.out.println("No puedes llevarte más: ya tienes " + tiene + " de " + maximo + ".");
                } else {
                    System.out.printf("Préstamo hecho: «%s» durante %d días (%d de %d libros).%n",
                            titulo, dias, tiene + 1, maximo);
                }
                break;
            }
            case 2: {
                System.out.print("Tipo de socio (estudiante/pdi/visitante): ");
                String tipo = sc.nextLine().trim().toLowerCase();
                System.out.print("Días de retraso: ");
                int retraso = sc.nextInt();

                if (retraso < 0) {
                    System.out.println("Dato no válido.");
                } else if (retraso == 0) {
                    System.out.println("Devuelto a tiempo. Gracias.");
                } else if (tipo.equals("pdi")) {
                    System.out.println("Devuelto con " + retraso + " días de retraso: el PDI no paga multa.");
                } else {
                    double multa = retraso * 0.20;     // 20 céntimos por día...
                    if (multa > 5) {
                        multa = 5;                     // ...con un máximo de 5 €
                    }
                    System.out.printf("Multa: %.2f € (0,20 € por día, como mucho 5 €).%n", multa);
                }
                break;
            }
            case 3:
                System.out.println("Estudiantes: 3 libros, 15 días.");
                System.out.println("PDI: 6 libros, 30 días.");
                System.out.println("Visitantes: 1 libro, 7 días.");
                System.out.println("Retraso: 0,20 € por día, como mucho 5 € (el PDI no paga).");
                break;
            default:
                System.out.println("Opción no válida: " + opcion);
        }
        sc.close();
    }
}

Por qué está hecho así:

  • Llaves propias en case 1: { … } y case 2: { … }. Todo el switch es un solo bloque: si los dos casos declararan String tipo sin llaves, javac diría variable tipo is already defined. Con llaves, cada tipo vive solo dentro de su caso. Fíjate en que el break va dentro de las llaves, al final.
  • El switch de dentro da valor a dos variables. Por eso es clásico (con break) y no una expresión: un switch expresión devuelve un valor. El default pone dias = 0 como marca de «tipo desconocido», igual que el -1 del certificado en la práctica.
  • El orden de la cadena del préstamo. Primero el tipo (si no existe, maximo es 0 y el mensaje de «ya tienes 1 de 0» no tendría sentido), después el título, después el límite. Con el visitante: 1 >= 1 es verdadero, así que no puede llevarse otro.
  • tiene + 1 en el mensaje. Tenía 1 y se lleva otro: 2 de 3.
  • La multa con tope. 9 × 0,20 = 1,80 €; 40 × 0,20 = 8,00 €, que pasa de 5 y se queda en 5,00 €. Una alternativa de una línea es Math.min(retraso * 0.20, 5).
  • PDI en mayúsculas funciona porque se pasa a minúsculas al leerlo; sin toLowerCase(), "PDI".equals("pdi") sería false y el profesor pagaría multa.

En el caso 2, sc.nextInt() es la última lectura, así que no hace falta el nextLine() de limpieza: no hay nada después que pueda tragarse el Intro. En el caso 1 sí, porque después viene el título.

Solución Ejercicio 3

import java.util.Scanner;

/**
 * Cajero automático: PIN con 3 intentos SIN bucle (un if dentro de otro) y una operación con switch.
 * Se leen siempre líneas (nextLine + parseInt): así no hay trampa del buffer.
 */
public class CajeroPIN {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        final String PIN = "4321";                 // String y no int: un PIN como 0420 perdería el cero
        final int LIMITE = 600;                    // euros por operación
        double saldo = 500.0;
        int intentos = 3;

        System.out.println("=== CAJERO AUTOMÁTICO ===");
        System.out.print("PIN: ");
        String pin = sc.nextLine().trim();
        if (!pin.equals(PIN)) {                    // fallo 1
            intentos--;
            System.out.println("PIN incorrecto (" + intentos + " intentos restantes)");
            System.out.print("PIN: ");
            pin = sc.nextLine().trim();
            if (!pin.equals(PIN)) {                // fallo 2
                intentos--;
                System.out.println("PIN incorrecto (" + intentos + " intento restante)");
                System.out.print("PIN: ");
                pin = sc.nextLine().trim();
                if (!pin.equals(PIN)) {            // fallo 3: se acabó
                    System.out.println("Tarjeta bloqueada. Llama a tu banco.");
                    return;                        // sin esto, el programa seguiría hasta el menú
                }
            }
        }
        // Si llegamos aquí es porque algún intento acertó
        System.out.println("PIN correcto.");
        System.out.println();
        System.out.println("1. Consultar saldo");
        System.out.println("2. Sacar dinero");
        System.out.println("3. Ingresar dinero");
        System.out.print("Operación: ");
        int op = Integer.parseInt(sc.nextLine().trim());

        switch (op) {
            case 1 -> System.out.printf("Saldo: %.2f €%n", saldo);
            case 2 -> {
                System.out.print("Cantidad (múltiplo de 10): ");
                int cantidad = Integer.parseInt(sc.nextLine().trim());
                // De lo más general a lo más concreto: formato, límite del cajero y, por último, el saldo
                if (cantidad <= 0 || cantidad % 10 != 0) {
                    System.out.println("Cantidad no válida: el cajero solo da billetes de 10, 20 y 50.");
                } else if (cantidad > LIMITE) {
                    System.out.println("Supera el límite por operación (" + LIMITE + " €).");
                } else if (cantidad > saldo) {
                    System.out.printf("Saldo insuficiente: tienes %.2f €.%n", saldo);
                } else {
                    saldo -= cantidad;
                    System.out.printf("Retira %d €. Saldo: %.2f €%n", cantidad, saldo);
                }
            }
            case 3 -> {
                System.out.print("Cantidad a ingresar: ");
                int cantidad = Integer.parseInt(sc.nextLine().trim());
                if (cantidad > 0 && cantidad <= 1000) {
                    saldo += cantidad;
                    System.out.printf("Ingreso hecho. Saldo: %.2f €%n", saldo);
                } else {
                    System.out.println("Cantidad no válida (de 1 a 1000 € por ingreso).");
                }
            }
            default -> System.out.println("Operación no válida.");
        }
        sc.close();
    }
}

Por qué está hecho así:

  • Tres if anidados. El segundo PIN solo se pide si el primero falló, y el tercero si fallaron los dos: cada intento va dentro del anterior. El dibujo enseña los caminos:
El PIN con tres ifs anidadosTres rombos seguidos: cada No pasa al siguiente intento restando uno a intentos; cada Sí baja al menú. El tercer No va a Tarjeta bloqueada y return. En la ejecución real se teclea 1111 (No) y 4321 (Sí).¿PIN 1 bien?teclea 1111Nointentos–Sí¿PIN 2 bien?teclea 4321Nointentos–Sí¿PIN 3 bien?SíNoPIN correcto → menú de operaciones (switch)bloqueadareturn;Ejecución real: 1111 (No) → 4321 (Sí). Sin el return, el «No» del tercero caería en el menú.
Todos los caminos con Sí acaban en el mismo menú; el único que no, termina con return.
  • El return del bloqueo es imprescindible. Sin él, tras el tercer fallo el programa saldría de los tres if y llegaría a «PIN correcto.». En la práctica, el adivino usaba la otra forma (un bloque detrás de otro y return al acertar); las dos son válidas, y las dos piden a gritos un bucle.
  • El PIN como String. Con int, 0420 se leería como 420 y un PIN de tres cifras pasaría por bueno. Además, comparar con equals evita el NumberFormatException si alguien teclea letras.
  • Leer siempre líneas. Integer.parseInt(sc.nextLine().trim()) no deja ningún Intro en el buffer, así que no hace falta el nextLine() de limpieza en ningún sitio.
  • El orden de las comprobaciones de la retirada. Primero el formato (positiva y múltiplo de 10), después el límite del cajero y por último el saldo. Con 550: es múltiplo de 10 y no pasa de 600, así que llega a la tercera: 550 > 500, saldo insuficiente. Con 125: 125 % 10 es 5, distinto de 0, y se para en la primera.
  • Las cuentas. 500 − 120 = 380,00 €; 500 + 200 = 700,00 €.
TecleadoCondiciónResultado
1111!"1111".equals("4321") → trueintentos 3 → 2, se pide otro
4321!"4321".equals("4321") → falsesale de los if: PIN correcto
2, 120120 % 10 == 0, 120 <= 600, 120 <= 500saldo 380,00 €

Cuestionario tipo examen

Ahora ponte a prueba como en el examen. El cuestionario de Java de FP2 es así: 15 preguntas en 15 minutos sobre todo el temario de Java, de las que te tocan unas pocas de cada tema. Aquí tienes las de control de flujo: contesta sin ejecutar nada, corrige y lee la explicación de las que falles. Estas preguntas son también el banco del simulacro de teoría de FP2.

Pregunta 1Única

¿Qué imprime este fragmento?

int nota = 7;
if (nota >= 5) System.out.print("A");
else if (nota >= 7) System.out.print("B");
else System.out.print("C");
System.out.println();
  1. A
  2. B
  3. AB
  4. C
Solución

Respuesta: A

Las condiciones se prueban de arriba abajo y se ejecuta la primera que se cumpla. Como 7 >= 5, sale A y el else if (nota >= 7) no llega a evaluarse. Por eso en una cadena de rangos se empieza por el más restrictivo.

Pregunta 2Respuesta corta

¿Qué imprime este fragmento?

int dia = 2;
switch (dia) {
    case 1: System.out.print("L");
    case 2: System.out.print("M");
    case 3: System.out.print("X"); break;
    default: System.out.print("?");
}
System.out.println();
Solución

Respuesta: MX

El switch clásico salta al case 2 e imprime M, pero como no hay break, sigue cayendo al case 3 (imprime X) hasta encontrar el break. Es el famoso fall-through: olvidar un break es uno de los errores más típicos.

Pregunta 3Verdadero o falso

Indica si es verdadero o falso:

  1. En Java no existe elif: se escribe else if.
  2. Con int x, la condición if (x = 5) no compila.
  3. Desde Java 7 se puede hacer un switch sobre un String.
  4. a && b evalúa siempre las dos partes.
  5. En un switch con flechas (case 1 -> …) no hay fall-through.
Solución
  • V — En Java no existe elif: se escribe else if.
  • V — Con int x, la condición if (x = 5) no compila.
  • V — Desde Java 7 se puede hacer un switch sobre un String.
  • F — a && b evalúa siempre las dos partes.
  • V — En un switch con flechas (case 1 -> …) no hay fall-through.

if (x = 5) asigna en lugar de comparar y el resultado es un int, no un boolean: Java no lo acepta (en C sí compilaría). && es de cortocircuito: si la primera parte es falsa, la segunda no se evalúa. Y el switch con -> ejecuta solo su rama, sin necesitar break.

Pregunta 4Respuesta corta

¿Qué imprime este fragmento?

int a = 4, b = 9;
String r = (a > b) ? "a" : (b % 2 == 0 ? "b par" : "b impar");
System.out.println(r);
Solución

Respuesta: b impar

El operador ternario es condición ? siSí : siNo. 4 > 9 es falso, así que se evalúa el segundo ternario: 9 % 2 es 1, no 0, por lo que el resultado es "b impar". Es el equivalente a x if c else y de Python.

Pregunta 5Única

¿Qué imprime este fragmento?

String s = new String("hola");
if (s == "hola") System.out.println("igual");
else System.out.println("distinto");
  1. distinto
  2. igual
  3. No compila
  4. Lanza una excepción
Solución

Respuesta: distinto

Con objetos, == mira si las dos variables apuntan al mismo sitio de memoria. Para comparar el texto se usa equals (o equalsIgnoreCase). Con String el fallo es traicionero porque a veces == acierta por casualidad (literales iguales comparten objeto).

Pregunta 6Respuesta corta

¿Qué imprime este fragmento?

int x = 0;
if (x != 0 && 10 / x > 1) System.out.println("grande");
else System.out.println("seguro");
Solución

Respuesta: seguro

x != 0 es falso, así que && ya sabe que todo es falso y no evalúa 10 / x. Gracias a eso no hay ArithmeticException por dividir entre cero. Con & (sin cortocircuito) sí se evaluaría y el programa fallaría.

Pregunta 7Rellenar huecos

Completa las reglas del if en Java:

La condición va siempre entre ____ y tiene que ser de tipo ____. El bloque va entre ____; si se omiten, del if solo depende la ____ sentencia.

Palabras:

  • paréntesis
  • boolean
  • primera
  • corchetes
  • sangría
  • última
  • llaves
  • int
Solución

La condición va siempre entre paréntesis y tiene que ser de tipo boolean. El bloque va entre llaves; si se omiten, del if solo depende la primera sentencia.

Sin llaves, la sangría no significa nada para Java: solo la primera sentencia queda dentro del if y las siguientes se ejecutan siempre. Por eso se recomienda poner llaves aunque haya una sola línea.

Pregunta 8Respuesta corta

¿Qué imprime este fragmento (Java 14 o posterior)?

String dia = "sab";
String tipo = switch (dia) {
    case "sab", "dom" -> "finde";
    default -> "laborable";
};
System.out.println(tipo);
Solución

Respuesta: finde

El switch moderno puede devolver un valor y agrupar varios casos con comas. "sab" encaja en el primer caso, así que tipo vale "finde". Fíjate en el ; final: todo el switch es una expresión dentro de una asignación.

Pregunta 9Relacionar

Relaciona cada operador con lo que hace:

&&
||
!
==
%

Definiciones:

  • Resto de la división entera
  • O lógico con cortocircuito
  • Potencia
  • Concatenación de condiciones
  • Y lógico con cortocircuito
  • Igualdad (en objetos compara referencias)
  • Negación
Solución
  • && → Y lógico con cortocircuito
  • || → O lógico con cortocircuito
  • ! → Negación
  • == → Igualdad (en objetos compara referencias)
  • % → Resto de la división entera

En Java no se escribe and, or ni not como en Python: son &&, || y !. La potencia no es un operador (Math.pow(a, b)) y % da el resto (9 % 2 es 1).

Pregunta 10Única

¿Qué imprime este fragmento?

int t = 20;
if (t > 25)
    System.out.print("calor ");
    System.out.print("playa");
System.out.println();
  1. playa
  2. Una línea vacía
  3. calor playa
  4. No compila
Solución

Respuesta: playa

La sangría hace creer que las dos líneas van dentro del if, pero Java solo mira las llaves. Como no hay, del if depende únicamente print("calor "). Es el error de tener un if sin llaves al que luego se le añade una línea.

Pregunta 11Múltiple · penaliza

Con int a = 5, b = 0;, ¿qué condiciones son verdaderas?

  1. a > 3 && b == 0
  2. a < 3 || b == 0
  3. a % 2 == 1
  4. !(a == 5)
  5. b != 0 && a / b > 1
Solución

Respuesta: a > 3 && b == 0 · a < 3 || b == 0 · a % 2 == 1

&& necesita las dos partes ciertas; ||, al menos una. 5 % 2 es 1, así que la tercera es cierta. La última es falsa sin lanzar ninguna excepción gracias al cortocircuito.

Míralo paso a paso en Python Tutor

Python Tutor (pythontutor.com) ejecuta el código línea a línea y marca cuál se ejecuta en cada paso. Elige Java en el desplegable y pega este programa, que clasifica el mismo 9,5 con el orden equivocado y con el bueno:

public class TutorNota {
    public static void main(String[] args) {
        double nota = 9.5;
        String mal;
        if (nota >= 5) {
            mal = "Suficiente";
        } else if (nota >= 9) {
            mal = "Muy bueno";
        } else {
            mal = "No apto";
        }
        String bien;
        if (nota >= 9) {
            bien = "Muy bueno";
        } else if (nota >= 5) {
            bien = "Suficiente";
        } else {
            bien = "No apto";
        }
        System.out.println(mal + " / " + bien);
    }
}

Salida:

Suficiente / Muy bueno

Qué observar paso a paso:

  1. En la primera cadena, la flecha pasa por if (nota >= 5), entra y salta directamente a la línea de String bien;: los else if ni se visitan.
  2. En el panel de variables aparece mal = "Suficiente" mientras nota sigue valiendo 9.5.
  3. En la segunda cadena, la primera condición (nota >= 9) ya es verdadera: entra a la primera.
  4. Cuenta los pasos de cada cadena: las dos se paran en cuanto encuentran una condición verdadera. Lo que cambia es cuál encuentran primero.

Chuletario: control de flujo en Java

Lo esencial en 30 segundos: valida el dato y corta con return antes de calcular; en una cadena else if gana la primera condición verdadera, así que lo más restrictivo va primero; los textos se comparan con equals(); «fuera de rango» es || y «dentro» es &&; en un switch clásico cada caso necesita su break (o usa flechas); leer líneas con nextLine() + parseInt evita la trampa del buffer.

Quiero…CódigoResultado
Comprobar que está dentro de un rangoif (x >= 0 && x <= 14)las dos a la vez
Detectar que está fueraif (x < 0 || x > 14)basta con una
Clasificar por tramosif (n >= 11) … else if (n >= 9) … else …de mayor a menor
Comparar textosa.equals(b) / a.equalsIgnoreCase(b)true si dicen lo mismo
Comparar con un fijo sin riesgo de null"s".equalsIgnoreCase(r)nunca lanza excepción
Terminar el programa antesreturn; dentro de mainse acaba ahí
Elegir según un valorswitch (op) { case 1 -> …; default -> …; }sin fall-through
Calcular según un valorint d = switch (t) { case "a" -> 15; default -> 0; };default obligatorio
Acumular a propósitocase 0: x += p; case 1: x += p; break;cae de un caso al siguiente
Variable propia en un casecase 1: { String t = …; break; }llaves propias
Leer un número sin trampaInteger.parseInt(sc.nextLine().trim())no deja Intro
Un número al azar repetiblenew Random(42).nextInt(100) + 1de 1 a 100, siempre el mismo

Plantillas de los ejercicios:

EjercicioPatrónCon estos datosSale
Nota de accesovalidar + return, else if de mayor a menor, printf("%.2f")11,8Excelente, 1,30 por encima
Bibliotecaswitch del menú con llaves por caso + switch de Stringestudiante, 1 libro15 días (2 de 3 libros)
Biblioteca (multa)if con tope40 días de retraso5,00 €
Cajerotres if (!pin.equals(PIN)) anidados + return1111, 4321, sacar 120saldo 380,00 €
SíntomaCausa probable
Notas altas clasificadas como bajaselse if ordenados de menor a mayor con solo el límite de abajo
Dos textos iguales «no coinciden»comparados con == en lugar de equals()
Un switch da el valor del último casofalta el break (fall-through)
Se aceptan datos imposibles&& donde iba `` (la condición nunca es verdadera)
variable … is already defined en un switchdos case declaran la misma variable sin llaves propias
variable … might not have been initializedalguna rama del if no da valor a la variable
El programa sigue después del bloqueofalta el return;
InputMismatchException con un decimalse tecleó punto en un equipo en español (o al revés)

Antes de entregar, pregúntate:

  1. ¿He probado un valor de cada rama, también los límites (5, 7, 9, 11) y uno fuera de rango?
  2. ¿Todos los textos se comparan con equals()?
  3. ¿Cada case de un switch clásico termina en break, salvo los que caen a propósito?
  4. ¿Valido el dato antes de usarlo (antes de substring, de dividir o de clasificar)?

Con esto cierras el control de flujo. En el tema siguiente, bucles en Java, el adivino y el cajero dejan de copiar su bloque tres veces: un while repite lo que haga falta.

Sigue aprendiendoTodos los artículos de Java

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →