Al final de la lección anterior escribiste un esbozo de menú para BiblioTech y lo despachaste con una escalera de if-else: if (opcion.equals("1")) … else if (opcion.equals("2")) …. Funciona, pero repite tres veces la misma variable, tres veces la misma llamada a equals y no comunica bien la intención. Cuando todas las ramas comparan la misma variable contra valores concretos, Java ofrece una construcción específica: el switch. Esta lección la cubre por completo, incluidas sus dos caras: el switch clásico heredado de C, con el fall-through que es la fuente número uno de bugs en este terreno, y el switch moderno con flechas introducido en Java 14, que elimina el problema de raíz y además permite usar switch como una expresión que devuelve un valor. Al terminar tendrás escrito el despachador de opciones del menú de BiblioTech, la pieza central del proyecto del módulo.

Contenido

  1. Qué problema resuelve switch
  2. El switch clásico: case, break, default
  3. El fall-through: por qué existe, cuándo se aprovecha y por qué muerde
  4. Tipos admitidos en un switch
  5. El switch con flechas de Java 14+
  6. El switch como expresión y la palabra clave yield
  7. Tabla comparativa: clásico, moderno y escalera if-else
  8. Pattern matching para switch (adelanto)
  9. BiblioTech: el despachador del menú
  10. BiblioTech: clasificación textual del estado de un préstamo
  11. Errores Comunes y Consejos
  12. Ejercicios

  1. Qué problema resuelve switch

Compara estas dos formas de hacer lo mismo:

// Escalera: la variable 'opcion' y el método equals aparecen tres veces
if (opcion.equals("1")) {
    System.out.println("Consultar catalogo");
} else if (opcion.equals("2")) {
    System.out.println("Registrar prestamo");
} else if (opcion.equals("3")) {
    System.out.println("Registrar devolucion");
} else {
    System.out.println("Opcion no reconocida");
}
// switch: la variable aparece UNA vez; debajo solo están los valores
switch (opcion) {
    case "1" -> System.out.println("Consultar catalogo");
    case "2" -> System.out.println("Registrar prestamo");
    case "3" -> System.out.println("Registrar devolucion");
    default  -> System.out.println("Opcion no reconocida");
}

La segunda versión no es solo más corta: expresa exactamente lo que ocurre, que es seleccionar entre valores discretos de una única variable. Ese es el dominio del switch, y también su límite:

Situación Herramienta adecuada
Una variable comparada con valores concretos (== o equals) switch
Rangos (diasRetraso > 7) escalera if-else
Condiciones compuestas de varias variables escalera if-else
Comprobaciones de nulidad, startsWith, contains escalera if-else

Un switch no puede expresar "si los días están entre 1 y 7". Si tu lógica es de rangos, quédate con la escalera de la lección 02-01.

  1. El switch clásico: case, break, default

La forma tradicional, disponible desde la primera versión de Java:

switch (expresion) {
    case valor1:
        // instrucciones
        break;
    case valor2:
        // instrucciones
        break;
    default:
        // instrucciones si no coincide ninguno
}

Cada palabra tiene un papel exacto:

  • switch (expresion) — la expresión se evalúa una sola vez; su valor se compara con cada case.
  • case valor: — una etiqueta. El valor debe ser una constante en tiempo de compilación: un literal, una constante final o una constante de enumeración. No puede ser una variable ni una expresión calculada.
  • break; — abandona el switch. Sin él, la ejecución continúa hacia el siguiente case.
  • default: — la rama de respaldo. Es opcional, pero omitirla suele ser un error de diseño: significa que no has pensado qué hacer con los valores inesperados.

Ejemplo completo con el estado de un ejemplar de la biblioteca:

package com.nexussoftware.bibliotech;

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

        char codigoEstado = 'P';   // D=disponible, P=prestado, R=reparacion, B=baja

        switch (codigoEstado) {
            case 'D':
                System.out.println("DISPONIBLE - se puede prestar");
                break;
            case 'P':
                System.out.println("PRESTADO - consultar fecha de devolucion");
                break;
            case 'R':
                System.out.println("EN REPARACION - no disponible temporalmente");
                break;
            case 'B':
                System.out.println("DE BAJA - retirado del catalogo");
                break;
            default:
                System.out.println("CODIGO DESCONOCIDO: " + codigoEstado);
        }
    }
}

El flujo, visto como diagrama:

flowchart TD
    S["Evaluar codigoEstado"] --> D1{"'D'"}
    D1 -- "coincide" --> A1["DISPONIBLE"] --> F["Fin del switch"]
    D1 -- "no" --> D2{"'P'"}
    D2 -- "coincide" --> A2["PRESTADO"] --> F
    D2 -- "no" --> D3{"'R'"}
    D3 -- "coincide" --> A3["EN REPARACION"] --> F
    D3 -- "no" --> D4{"'B'"}
    D4 -- "coincide" --> A4["DE BAJA"] --> F
    D4 -- "no" --> DEF["default: desconocido"] --> F

Detalle práctico: el default no tiene por qué ir al final (puede ir en medio y funciona igual), pero ponerlo al final es la convención universal y no hay ninguna razón para desviarse. El último case o el default no necesitan break, porque el switch termina ahí de todos modos; aun así, muchos equipos lo escriben por si alguien añade una rama después.

  1. El fall-through: por qué existe, cuándo se aprovecha y por qué muerde

Fall-through (caída) significa que, al terminar las instrucciones de un case, la ejecución continúa en el case siguiente en lugar de salir del switch. No es un accidente del lenguaje: es su comportamiento por diseño, heredado de C.

Observa qué pasa si se olvida un break:

char codigoEstado = 'D';

switch (codigoEstado) {
    case 'D':
        System.out.println("DISPONIBLE");
        // ¡falta el break!
    case 'P':
        System.out.println("PRESTADO");
        break;
    case 'R':
        System.out.println("EN REPARACION");
        break;
}

Salida:

DISPONIBLE
PRESTADO

El libro se declara simultáneamente disponible y prestado. El compilador no avisa, porque el fall-through es legal. Este bug es difícil de detectar leyendo, porque la vista salta de case en case y el break ausente es invisible. Por eso mismo es el fallo más reportado en revisiones de código sobre switch clásico.

El uso legítimo: agrupar casos. Cuando varios valores comparten el mismo tratamiento, se apilan las etiquetas sin instrucciones entre ellas:

char codigoEstado = 'R';

switch (codigoEstado) {
    case 'D':
        System.out.println("El ejemplar SE PUEDE prestar.");
        break;

    // Tres códigos distintos, un único tratamiento: fall-through intencionado
    case 'P':
    case 'R':
    case 'B':
        System.out.println("El ejemplar NO se puede prestar ahora mismo.");
        break;

    default:
        System.out.println("Codigo desconocido.");
}

Aquí case 'P': y case 'R': no tienen cuerpo propio: caen deliberadamente hasta el bloque de case 'B':. Es idiomático y perfectamente aceptable.

La regla profesional, adoptada por todas las guías de estilo serias: si un case con instrucciones cae al siguiente a propósito, documéntalo con un comentario explícito:

case 'P':
    System.out.println("Registrando devolucion previa...");
    // fall-through intencionado: continua con el tratamiento comun
case 'R':
    System.out.println("Ejemplar no prestable.");
    break;

Sin ese comentario, quien lea el código no podrá distinguir tu intención de un olvido. Y quien lo mantenga dentro de seis meses serás probablemente tú.

  1. Tipos admitidos en un switch

No todo puede ir dentro de los paréntesis de un switch. Los tipos permitidos son:

Tipo ¿Admitido? Desde Nota
byte, short, char, int Java 1.0 Los enteros pequeños; long no está permitido
Byte, Short, Character, Integer Java 5 Envoltorios, con autoboxing
enum Java 5 El uso más natural del switch; lo verás en la lección 04-07
String Java 7 Compara por contenido, como equals
long, float, double, boolean No Usa if-else
Cualquier otro objeto Solo con pattern matching Java 21 Ver apartado 8

Dos precisiones importantes sobre String:

  1. La comparación es por contenido, equivalente a equals. No caes en la trampa del == que viste en la lección 02-01.
  2. Es sensible a mayúsculas: case "si" no captura "SI". La solución habitual es normalizar antes: switch (respuesta.toLowerCase()).
  3. Si la variable vale null, un switch clásico sobre String detiene el programa con NullPointerException. Protégete con un if antes, o normaliza con trim() sobre un valor que sepas no nulo.

Sobre long: la exclusión sorprende, pero tiene una explicación histórica ligada a las instrucciones de bytecode que implementan el switch. En la práctica no es un problema: si necesitas conmutar sobre un long, o conviertes con (int) cuando sea seguro, o usas if-else.

  1. El switch con flechas de Java 14+

Java 14 estabilizó una sintaxis alternativa que resuelve el fall-through eliminándolo por completo:

switch (codigoEstado) {
    case 'D' -> System.out.println("DISPONIBLE - se puede prestar");
    case 'P' -> System.out.println("PRESTADO - consultar fecha de devolucion");
    case 'R' -> System.out.println("EN REPARACION");
    case 'B' -> System.out.println("DE BAJA");
    default  -> System.out.println("CODIGO DESCONOCIDO: " + codigoEstado);
}

Diferencias respecto al clásico, una por una:

  • Se usa -> en lugar de :.
  • No hay break, y no puede haberlo: cada rama es independiente y al terminar sale del switch. El fall-through simplemente no existe.
  • Para agrupar valores se separan por comas en la misma etiqueta, en lugar de apilar etiquetas:
switch (codigoEstado) {
    case 'D'           -> System.out.println("Se puede prestar.");
    case 'P', 'R', 'B' -> System.out.println("No se puede prestar ahora mismo.");
    default            -> System.out.println("Codigo desconocido.");
}
  • Si una rama necesita varias instrucciones, se envuelve en llaves:
switch (opcion) {
    case "1" -> {
        System.out.println("=== CATALOGO DE BIBLIOTECH ===");
        System.out.println("Java Efectivo (978-0000000001)");
        System.out.println("Patrones de Diseno (978-0000000002)");
        System.out.println("Refactorizacion (978-0000000003)");
    }
    case "0" -> System.out.println("Saliendo...");
    default  -> System.out.println("Opcion no valida.");
}

Java 17, la versión de referencia de este curso, admite ambas sintaxis. Para código nuevo, usa siempre la de flechas: es más corta, más segura y elimina de un plumazo la clase de bug más frecuente de esta construcción. La sintaxis clásica sigue siendo imprescindible para leer código existente, que es la mitad del trabajo de un desarrollador.

  1. El switch como expresión y la palabra clave yield

La aportación más potente de Java 14 no es la flecha, sino que el switch puede ser una expresión: algo que produce un valor y se puede asignar a una variable, exactamente igual que el operador ternario, pero con tantas ramas como quieras.

int diasRetraso = 12;
final int UMBRAL_LEVE = 7;

// switch como SENTENCIA: hace cosas
switch (codigoEstado) {
    case 'D' -> System.out.println("Disponible");
    default  -> System.out.println("No disponible");
}

// switch como EXPRESIÓN: produce un valor (fíjate en el '=' y en el ';' final)
String descripcion = switch (codigoEstado) {
    case 'D' -> "DISPONIBLE";
    case 'P' -> "PRESTADO";
    case 'R' -> "EN REPARACION";
    case 'B' -> "DE BAJA";
    default  -> "DESCONOCIDO";
};
System.out.println("Estado del ejemplar: " + descripcion);

Tres reglas gobiernan el switch como expresión:

  1. Debe ser exhaustivo: para cualquier valor posible tiene que haber una rama. Con char o String eso obliga a incluir default. Si falta, no compila (the switch expression does not cover all possible input values). Esa comprobación del compilador es oro puro: hace imposible olvidar un caso.
  2. Termina con punto y coma después de la llave de cierre, porque es parte de una sentencia de asignación.
  3. Todas las ramas deben producir un valor del mismo tipo (o compatible).

yield: devolver un valor desde un bloque. Cuando una rama necesita varias líneas antes de producir el valor, se usan llaves y la última instrucción es yield:

final double TARIFA_DIARIA = 0.25;
final double MULTA_MAXIMA = 20.0;
String tipoUsuario = "EXTERNO";
int diasRetraso = 30;

double multa = switch (tipoUsuario) {
    case "EMPLEADO" -> diasRetraso * TARIFA_DIARIA;

    case "BECARIO" -> {
        // Los becarios pagan la mitad: hacen falta varias instrucciones
        double base = diasRetraso * TARIFA_DIARIA;
        double conDescuento = base / 2;
        yield conDescuento;                  // 'yield' entrega el valor de la rama
    }

    case "EXTERNO" -> {
        double base = diasRetraso * TARIFA_DIARIA * 2;   // recargo del 100 %
        yield Math.min(base, MULTA_MAXIMA);
    }

    default -> 0.0;
};

System.out.printf("Multa para %s: %.2f EUR%n", tipoUsuario, multa);
// Multa para EXTERNO: 15,00 EUR

yield es a un bloque de switch lo que return es a un método: entrega el resultado. No lo confundas con break, que en el switch clásico solo salía sin devolver nada.

También existe la combinación de sintaxis clásica con expresión (case 'D': yield "DISPONIBLE";), pero mezclar : y -> en el mismo switch es un error de compilación, y en la práctica nadie usa esa variante. Quédate con las flechas.

  1. Tabla comparativa: clásico, moderno y escalera if-else

Criterio switch clásico switch con flechas Escalera if-else
Sintaxis de rama case v: + break case v -> if (…) { }
Riesgo de fall-through Alto Ninguno No aplica
Agrupar valores Etiquetas apiladas case a, b, c -> || en la condición
Puede devolver un valor No Sí (expresión) No (requiere ternario)
Exhaustividad verificada No Sí, en expresiones No
Rangos y condiciones compuestas No No
Varias variables en la decisión No No
Legibilidad con 5+ ramas Media Alta Baja
Disponible desde Java 1.0 Java 14 Java 1.0

Criterio de elección, en una frase: si comparas una sola variable contra valores concretos, switch con flechas; si hay rangos, nulos o varias variables, escalera if-else.

  1. Pattern matching para switch (adelanto)

Java 21 llevó el switch un paso más allá con el pattern matching: las etiquetas case pueden comprobar el tipo de un objeto y extraer sus datos en el mismo gesto.

// Java 21+. Sirve como muestra: no lo uses todavía.
String descripcion = switch (objeto) {
    case Integer i when i > 100 -> "Numero grande: " + i;
    case Integer i              -> "Numero: " + i;
    case String s               -> "Texto de " + s.length() + " caracteres";
    case null                   -> "Sin valor";
    default                     -> "Tipo no contemplado";
};

Fíjate en dos novedades: el case null (por fin el switch puede manejar nulos sin reventar) y la cláusula when, que añade una condición extra a la coincidencia.

Este código requiere entender clases, tipos de referencia y jerarquías, que llegan en los módulos 3 y 4. Se estudia en la lección 10-06 (Java 9 y Más Allá). Basta con que sepas que existe y que reconozcas la sintaxis si te la encuentras.

  1. BiblioTech: el despachador del menú

Con todo lo anterior ya puedes escribir la pieza que estructurará el proyecto del módulo: el bucle de menú de la lección 02-02 con su despacho resuelto por switch.

package com.nexussoftware.bibliotech;

import java.util.Scanner;

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

        final String NOMBRE_EMPRESA = "Nexus Software";
        final int DIAS_PRESTAMO = 15;
        final double TARIFA_DIARIA = 0.25;
        final double MULTA_MAXIMA = 20.0;

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

        while (!salir) {

            System.out.println();
            System.out.println("=== BIBLIOTECH - " + NOMBRE_EMPRESA + " ===");
            System.out.println("  1. Consultar catalogo");
            System.out.println("  2. Registrar prestamo");
            System.out.println("  3. Registrar devolucion");
            System.out.println("  0. Salir");
            System.out.print("Opcion: ");

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

            switch (opcion) {

                case "1" -> {
                    System.out.println("--- CATALOGO ---");
                    System.out.println("  Java Efectivo        978-0000000001");
                    System.out.println("  Patrones de Diseno   978-0000000002");
                    System.out.println("  Refactorizacion      978-0000000003");
                }

                case "2" -> {
                    System.out.print("  Empleado: ");
                    String empleado = scanner.nextLine().trim();
                    System.out.print("  Titulo: ");
                    String titulo = scanner.nextLine().trim();
                    System.out.printf("  PRESTAMO registrado: %s -> %s (%d dias)%n",
                                      titulo, empleado, DIAS_PRESTAMO);
                }

                case "3" -> {
                    System.out.print("  Dias transcurridos: ");
                    int diasTranscurridos = Integer.parseInt(scanner.nextLine().trim());

                    int diasRetraso = diasTranscurridos - DIAS_PRESTAMO;
                    if (diasRetraso < 0) {
                        diasRetraso = 0;
                    }

                    double multa = diasRetraso * TARIFA_DIARIA;
                    if (multa > MULTA_MAXIMA) {
                        multa = MULTA_MAXIMA;
                    }

                    System.out.printf("  DEVOLUCION: %d dias de retraso, multa %.2f EUR%n",
                                      diasRetraso, multa);
                }

                case "0" -> {
                    System.out.println("Cerrando BiblioTech. Hasta pronto.");
                    salir = true;
                }

                default -> System.out.println("  Opcion no reconocida. Usa 0, 1, 2 o 3.");
            }
        }

        scanner.close();
    }
}

Tres observaciones sobre el diseño:

  1. La opción se lee como String y no como int. Así, si el usuario escribe hola, cae en el default en lugar de detener el programa con NumberFormatException. Es una decisión defensiva barata y eficaz mientras no dispongas de excepciones (módulo 6).
  2. La salida se hace con la bandera salir, no con break. Y hay un motivo de peso, que es exactamente el tema de la lección siguiente: dentro de un bucle, un break situado en un switch saldría del switch, no del bucle, y eso ocurre igual con la sintaxis de flechas (case "0" -> { ...; break; } compila y no cierra el menú). La bandera evita el problema de raíz.
  3. Cada rama del switch va entre llaves porque contiene varias instrucciones, y las variables declaradas dentro (empleado, titulo, diasRetraso) solo existen en su bloque. Eso evita colisiones de nombres entre ramas.

Sesión de ejemplo:

=== BIBLIOTECH - Nexus Software ===
  1. Consultar catalogo
  2. Registrar prestamo
  3. Registrar devolucion
  0. Salir
Opcion: 1
--- CATALOGO ---
  Java Efectivo        978-0000000001
  Patrones de Diseno   978-0000000002
  Refactorizacion      978-0000000003

=== BIBLIOTECH - Nexus Software ===
...
Opcion: 3
  Dias transcurridos: 27
  DEVOLUCION: 12 dias de retraso, multa 3,00 EUR

=== BIBLIOTECH - Nexus Software ===
...
Opcion: 9
  Opcion no reconocida. Usa 0, 1, 2 o 3.

=== BIBLIOTECH - Nexus Software ===
...
Opcion: 0
Cerrando BiblioTech. Hasta pronto.

  1. BiblioTech: clasificación textual del estado de un préstamo

El segundo uso natural del switch en BiblioTech es traducir códigos internos a texto legible. Aquí el switch como expresión luce especialmente:

package com.nexussoftware.bibliotech;

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

        // Código de gravedad calculado por las reglas de negocio del modulo 1:
        // 0 = sin retraso, 1 = leve, 2 = grave, 3 = multa topada
        int gravedad = 2;
        String titulo = "Patrones de Diseno";

        // Una sola variable, cuatro valores discretos: caso de libro para switch.
        String estado = switch (gravedad) {
            case 0 -> "EN PLAZO";
            case 1, 2, 3 -> "RETRASADO";       // tres codigos, un mismo estado
            default -> "DESCONOCIDO";
        };

        String mensaje = switch (gravedad) {
            case 0 -> "Devolucion correcta. Gracias.";
            case 1 -> "Retraso leve. Se aplica la tarifa diaria.";
            case 2 -> "Retraso grave. Revisa el habito de devolucion.";
            case 3 -> {
                String aviso = "Multa maxima alcanzada.";
                yield aviso + " Se notificara a Recursos Humanos.";
            }
            default -> "Codigo de gravedad no contemplado: " + gravedad;
        };

        // El icono es un char: otro switch expresion, en una linea por rama.
        char icono = switch (gravedad) {
            case 0 -> '+';
            case 1 -> '!';
            case 2, 3 -> 'X';
            default -> '?';
        };

        System.out.printf("[%c] %-22s %-12s %s%n", icono, titulo, estado, mensaje);
    }
}

Salida:

[X] Patrones de Diseno     RETRASADO    Retraso grave. Revisa el habito de devolucion.

Compara esto con la alternativa del módulo 1: tres cadenas de ternarios anidados, ilegibles. El switch como expresión permite asignar una variable con varias ramas manteniendo la claridad, y el compilador te obliga a cubrir todos los casos.

Errores Comunes y Consejos

1. Olvidar el break en el switch clásico. El bug estrella. Se ejecutan ramas que no querías y el compilador calla. Solución definitiva: usa la sintaxis de flechas.

2. Poner break en un switch de flechas. Nunca hace falta para separar ramas, y según dónde lo escribas ocurren tres cosas distintas:

Dónde Resultado
case "0" -> break; No compila: unexpected statement in case, expected is an expression, a block or a throw statement
case "0" -> { … break; } dentro de un switch de sentencia Sí compila, y sale del switch, no del bucle que lo envuelve: la misma trampa del switch clásico
break dentro de un switch de expresión No compila: attempt to break out of a switch expression. Ahí se usa yield

La conclusión práctica es la misma en los tres casos: en la sintaxis de flechas, no escribas break.

3. Mezclar : y -> en el mismo switch. Error de compilación. Elige una forma y mantenla en todo el bloque.

4. Usar una variable como etiqueta de case.

int limite = 7;
switch (dias) {
    case limite:      // ERROR: constant expression required
        ...
}

Solo valen literales y constantes final inicializadas con literales. Si la etiqueta debe calcularse, tu problema no es de switch, sino de if-else.

5. Repetir un valor en dos case. case "1": ... case "1": es error de compilación (duplicate case label). Es una buena noticia: el switch detecta duplicados que en una escalera if-else pasarían inadvertidos.

6. Olvidar el default en un switch de expresión. No compila si el tipo no está cubierto por completo. En un switch de sentencia sí compila, pero deja un agujero silencioso: si llega un valor inesperado, no pasa nada en absoluto y nadie se entera.

7. switch sobre String que puede ser null. Detiene el programa. Valida antes:

if (opcion == null) {
    opcion = "";
}
switch (opcion) { ... }

8. Olvidar el punto y coma del switch como expresión.

String estado = switch (gravedad) {
    case 0 -> "EN PLAZO";
    default -> "RETRASADO";
}                       // <-- falta el ';' de la asignación

9. Consejo: normaliza la entrada antes de conmutar. switch (respuesta.trim().toLowerCase()) evita tener que escribir case "SI", "Si", "si", "sI".

10. Consejo: cuando las ramas crezcan, se te está pidiendo otra cosa. Un switch de veinte ramas con quince líneas cada una es señal de que hace falta descomponer en métodos (lección 03-03) o, más adelante, usar polimorfismo (lección 03-06), que es la forma orientada a objetos de eliminar switch gigantes.

Ejercicios

Ejercicio 1: cazar el fall-through olvidado

El siguiente código pretende clasificar el trimestre en que se adquirió un libro del catálogo de BiblioTech, pero tiene dos break olvidados. Ejecútalo mentalmente con mesAdquisicion = 2 y con mesAdquisicion = 5, anota la salida real, identifica los dos fallos y corrígelos. Después, reescribe la versión corregida usando switch con flechas.

int mesAdquisicion = 2;
String trimestre;

switch (mesAdquisicion) {
    case 1:
    case 2:
    case 3:
        trimestre = "1T";
    case 4:
    case 5:
    case 6:
        trimestre = "2T";
        break;
    case 7:
    case 8:
    case 9:
        trimestre = "3T";
        break;
    case 10:
    case 11:
    case 12:
        trimestre = "4T";
    default:
        trimestre = "MES NO VALIDO";
}

System.out.println("Adquirido en el trimestre: " + trimestre);

Ejercicio 2: menú de informes con switch de expresión

Escribe un programa que muestre este menú en bucle y use un switch como expresión para obtener el texto del informe seleccionado, y un switch de sentencia aparte para ejecutar la acción de salir:

1. Informe de multas
2. Informe de prestamos activos
3. Informe de libros mas retrasados
0. Salir

Requisitos:

  • Las opciones 1, 2 y 3 deben producir una cadena descriptiva mediante un switch de expresión asignado a una variable String informe.
  • La opción 3 debe usar un bloque con yield, porque construye su texto en dos pasos.
  • Cualquier otra entrada debe caer en default y mostrar un aviso sin romper el bucle.
  • Cuenta cuántos informes se han generado durante la sesión y muéstralo al salir.

Ejercicio 3: clasificación combinada

Escribe un programa que, dado un int diasRetraso, calcule primero un código de gravedad (0 = sin retraso, 1 = leve si <= UMBRAL_LEVE, 2 = grave, 3 = multa topada porque el importe alcanza MULTA_MAXIMA) usando una escalera if-else, y después use un switch de expresión sobre ese código para obtener a la vez la etiqueta textual, el importe efectivo de la multa y el mensaje para el empleado.

La clave del ejercicio es que veas dónde termina el if y dónde empieza el switch: los rangos los resuelve el if; los valores discretos resultantes los resuelve el switch. Prueba con diasRetraso igual a 0, 5, 30 y 200.

Soluciones

Solución 1

Comportamiento del código roto.

Con mesAdquisicion = 2: coincide case 2, cae hasta case 3 (sin instrucciones), asigna trimestre = "1T", y como falta el break, sigue cayendo por case 4 y case 5 hasta ejecutar trimestre = "2T"; y ahí sí encuentra un break. Salida: Adquirido en el trimestre: 2T. Incorrecto: febrero es 1T.

Con mesAdquisicion = 5: coincide case 5, asigna "2T", encuentra el break. Salida: 2T. Correcto por casualidad.

Y hay un tercer caso revelador, mesAdquisicion = 11: asigna "4T", cae al default y lo sobrescribe con "MES NO VALIDO". Salida errónea también.

Los dos break olvidados están al final del bloque "1T" y al final del bloque "4T".

Versión clásica corregida:

switch (mesAdquisicion) {
    case 1:
    case 2:
    case 3:
        trimestre = "1T";
        break;              // <-- AÑADIDO
    case 4:
    case 5:
    case 6:
        trimestre = "2T";
        break;
    case 7:
    case 8:
    case 9:
        trimestre = "3T";
        break;
    case 10:
    case 11:
    case 12:
        trimestre = "4T";
        break;              // <-- AÑADIDO
    default:
        trimestre = "MES NO VALIDO";
}

Versión moderna, donde el bug es imposible:

package com.nexussoftware.bibliotech;

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

        int mesAdquisicion = 2;

        // switch de expresión: exhaustivo, sin break y sin fall-through posible.
        String trimestre = switch (mesAdquisicion) {
            case 1, 2, 3    -> "1T";
            case 4, 5, 6    -> "2T";
            case 7, 8, 9    -> "3T";
            case 10, 11, 12 -> "4T";
            default         -> "MES NO VALIDO";
        };

        System.out.println("Adquirido en el trimestre: " + trimestre);   // 1T
    }
}

De veinticinco líneas a siete, y con una clase entera de bugs eliminada. Esta es la razón por la que el curso recomienda la sintaxis de flechas para todo el código nuevo.

Solución 2

package com.nexussoftware.bibliotech;

import java.util.Scanner;

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

        final String NOMBRE_EMPRESA = "Nexus Software";
        final double TARIFA_DIARIA = 0.25;

        Scanner scanner = new Scanner(System.in);
        boolean salir = false;
        int informesGenerados = 0;       // contador de la sesión

        while (!salir) {

            System.out.println();
            System.out.println("=== INFORMES BIBLIOTECH - " + NOMBRE_EMPRESA + " ===");
            System.out.println("  1. Informe de multas");
            System.out.println("  2. Informe de prestamos activos");
            System.out.println("  3. Informe de libros mas retrasados");
            System.out.println("  0. Salir");
            System.out.print("Opcion: ");

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

            // 'informe' vale null cuando la opcion no genera informe (0 o invalida).
            String informe = switch (opcion) {

                case "1" -> "MULTAS: 3 devoluciones con multa. "
                          + "Tarifa aplicada: " + TARIFA_DIARIA + " EUR/dia.";

                case "2" -> "PRESTAMOS ACTIVOS: Marta Ruiz (Java Efectivo), "
                          + "Diego Alonso (Patrones de Diseno).";

                case "3" -> {
                    // Rama de varias instrucciones: requiere llaves y yield.
                    String peor = "Refactorizacion";
                    int diasRetraso = 105;
                    yield "MAS RETRASADOS: " + peor + " con " + diasRetraso + " dias.";
                }

                default -> null;    // 0 y cualquier otra entrada
            };

            if (informe != null) {
                System.out.println("  " + informe);
                informesGenerados++;
            }

            // Segundo switch, ahora como SENTENCIA: ejecuta acciones.
            switch (opcion) {
                case "1", "2", "3" -> { /* ya se ha impreso arriba */ }
                case "0" -> {
                    System.out.printf("Sesion cerrada. Informes generados: %d%n",
                                      informesGenerados);
                    salir = true;
                }
                default -> System.out.println("  Opcion no reconocida.");
            }
        }

        scanner.close();
    }
}

Sesión de ejemplo:

Opcion: 1
  MULTAS: 3 devoluciones con multa. Tarifa aplicada: 0.25 EUR/dia.
Opcion: 3
  MAS RETRASADOS: Refactorizacion con 105 dias.
Opcion: 7
  Opcion no reconocida.
Opcion: 0
Sesion cerrada. Informes generados: 2

Nota sobre el diseño: usar null como "no hay informe" funciona, pero es exactamente la clase de decisión que produce NullPointerException en programas grandes. En el módulo 10 conocerás Optional, la forma moderna de representar "puede que no haya valor". Por ahora, la guarda if (informe != null) cumple.

Solución 3

package com.nexussoftware.bibliotech;

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

        final double TARIFA_DIARIA = 0.25;
        final double MULTA_MAXIMA = 20.0;
        final int UMBRAL_LEVE = 7;

        int diasRetraso = 30;
        String titulo = "Refactorizacion";
        String empleado = "Nuria Vidal";

        double multaTeorica = diasRetraso * TARIFA_DIARIA;

        // ---- FASE 1: los RANGOS los resuelve un if-else ----
        int gravedad;
        if (diasRetraso == 0) {
            gravedad = 0;                          // sin retraso
        } else if (multaTeorica >= MULTA_MAXIMA) {
            gravedad = 3;                          // multa topada
        } else if (diasRetraso <= UMBRAL_LEVE) {
            gravedad = 1;                          // leve
        } else {
            gravedad = 2;                          // grave
        }

        // ---- FASE 2: los VALORES DISCRETOS los resuelve un switch ----
        String etiqueta = switch (gravedad) {
            case 0 -> "EN PLAZO";
            case 1 -> "RETRASO LEVE";
            case 2 -> "RETRASO GRAVE";
            case 3 -> "MULTA MAXIMA";
            default -> "DESCONOCIDO";
        };

        double multaEfectiva = switch (gravedad) {
            case 0 -> 0.0;
            case 1, 2 -> multaTeorica;
            case 3 -> MULTA_MAXIMA;
            default -> 0.0;
        };

        String mensaje = switch (gravedad) {
            case 0 -> "Gracias por devolver a tiempo.";
            case 1 -> "Pequeno retraso. Intenta ajustarte al plazo.";
            case 2 -> "Retraso importante. Revisa tus prestamos activos.";
            case 3 -> {
                String base = "Se ha alcanzado el tope de " + MULTA_MAXIMA + " EUR.";
                yield base + " Notificacion enviada a Recursos Humanos.";
            }
            default -> "Sin mensaje.";
        };

        System.out.println("=== EVALUACION DE DEVOLUCION ===");
        System.out.printf("%-14s %s%n",       "Empleado:", empleado);
        System.out.printf("%-14s %s%n",       "Libro:", titulo);
        System.out.printf("%-14s %d%n",       "Dias retraso:", diasRetraso);
        System.out.printf("%-14s %s%n",       "Clasificacion:", etiqueta);
        System.out.printf("%-14s %.2f EUR%n", "Multa:", multaEfectiva);
        System.out.printf("%-14s %s%n",       "Mensaje:", mensaje);
    }
}

Resultados con los cuatro valores pedidos:

diasRetraso Multa teórica gravedad Etiqueta Multa efectiva
0 0,00 € 0 EN PLAZO 0,00 €
5 1,25 € 1 RETRASO LEVE 1,25 €
30 7,50 € 2 RETRASO GRAVE 7,50 €
200 50,00 € 3 MULTA MAXIMA 20,00 €

La lección del ejercicio es el reparto de responsabilidades: if-else traduce rangos continuos en un código discreto, y a partir de ahí el switch puede hacer su trabajo. Fíjate también en que el orden del if-else importa: la comprobación del tope va antes que la de UMBRAL_LEVE, porque una multa topada es siempre prioritaria sobre la clasificación por días. Y observa que tres switch distintos comparten el mismo código de gravedad; cuando llegues a la lección 04-07 verás que las enumeraciones son la forma correcta de representar ese código, y el compilador podrá incluso verificar que no falta ninguna rama sin necesidad de default.

Conclusión

Ya sabes despachar decisiones sobre valores discretos. Has visto el switch clásico con case, break y default; el fall-through, su razón de ser, su uso legítimo para agrupar casos y por qué un break olvidado es el bug más frecuente de esta construcción; los tipos admitidos (int y sus hermanos pequeños, char, String, enum) y los prohibidos (long, double, boolean); el switch con flechas de Java 14+, donde el fall-through es imposible y agrupar valores es cuestión de comas; y el switch como expresión, con yield para las ramas de varias líneas y con la exhaustividad verificada por el compilador. Con la tabla comparativa tienes el criterio: switch para valores concretos de una variable, if-else para rangos y condiciones compuestas.

BiblioTechApp tiene ya un menú en bucle con su despachador escrito con flechas, cuatro opciones operativas y una rama default que absorbe las entradas erróneas sin romper nada. También has visto que la salida se controla con una bandera booleana, y se ha anticipado el motivo: un break dentro de un switch no hace lo que un principiante espera.

Precisamente eso es lo que aclara la siguiente lección, Break y Continue: cómo abandonar un bucle en cuanto encuentras lo que buscabas, cómo saltar una iteración que no interesa, por qué el break de un switch anidado en un bucle es una trampa clásica, y qué alternativas —etiquetas, banderas— existen cuando necesitas romper varios niveles a la vez.

Curso de Programación en Java

Módulo 1: Introducción a Java

Módulo 2: Flujo de Control

Módulo 3: Programación Orientada a Objetos

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

Módulo 5: Estructuras de Datos y Colecciones

Módulo 6: Manejo de Excepciones

Módulo 7: Entrada/Salida de Archivos

Módulo 8: Multihilo y Concurrencia

Módulo 9: Redes

Módulo 10: Temas Avanzados

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

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

© Copyright 2026. Todos los derechos reservados