Al final de la lliçó anterior vas escriure un esbós de menú per a BiblioTech i el vas despatxar amb una escala d'if-else: if (opcio.equals("1")) … else if (opcio.equals("2")) …. Funciona, però repeteix tres vegades la mateixa variable, tres vegades la mateixa crida a equals i no comunica bé la intenció. Quan totes les branques comparen la mateixa variable contra valors concrets, Java ofereix una construcció específica: el switch. Aquesta lliçó la cobreix del tot, incloses les seves dues cares: el switch clàssic heretat de C, amb el fall-through que és la font número u d'errors en aquest terreny, i el switch modern amb fletxes introduït a Java 14, que elimina el problema d'arrel i a més permet fer servir switch com una expressió que retorna un valor. En acabar tindràs escrit el despatxador d'opcions del menú de BiblioTech, la peça central del projecte del mòdul.

Contingut

  1. Quin problema resol switch
  2. El switch clàssic: case, break, default
  3. El fall-through: per què existeix, quan s'aprofita i per què mossega
  4. Tipus admesos en un switch
  5. El switch amb fletxes de Java 14+
  6. El switch com a expressió i la paraula clau yield
  7. Taula comparativa: clàssic, modern i escala if-else
  8. Pattern matching per a switch (avançament)
  9. BiblioTech: el despatxador del menú
  10. BiblioTech: classificació textual de l'estat d'un préstec
  11. Errors Habituals i Consells
  12. Exercicis

  1. Quin problema resol switch

Compara aquestes dues formes de fer el mateix:

// Escala: la variable 'opcio' i el metode equals apareixen tres vegades
if (opcio.equals("1")) {
    System.out.println("Consultar cataleg");
} else if (opcio.equals("2")) {
    System.out.println("Registrar prestec");
} else if (opcio.equals("3")) {
    System.out.println("Registrar devolucio");
} else {
    System.out.println("Opcio no reconeguda");
}
// switch: la variable apareix UNA vegada; a sota nomes hi ha els valors
switch (opcio) {
    case "1" -> System.out.println("Consultar cataleg");
    case "2" -> System.out.println("Registrar prestec");
    case "3" -> System.out.println("Registrar devolucio");
    default  -> System.out.println("Opcio no reconeguda");
}

La segona versió no és només més curta: expressa exactament el que passa, que és seleccionar entre valors discrets d'una única variable. Aquest és el domini del switch, i també el seu límit:

Situació Eina adequada
Una variable comparada amb valors concrets (== o equals) switch
Rangs (diesRetard > 7) escala if-else
Condicions compostes de diverses variables escala if-else
Comprovacions de nul·litat, startsWith, contains escala if-else

Un switch no pot expressar "si els dies estan entre 1 i 7". Si la teva lògica és de rangs, queda't amb l'escala de la lliçó 02-01.

  1. El switch clàssic: case, break, default

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

switch (expressio) {
    case valor1:
        // instruccions
        break;
    case valor2:
        // instruccions
        break;
    default:
        // instruccions si no coincideix cap
}

Cada paraula té un paper exacte:

  • switch (expressio) — l'expressió s'avalua una sola vegada; el seu valor es compara amb cada case.
  • case valor: — una etiqueta. El valor ha de ser una constant en temps de compilació: un literal, una constant final o una constant d'enumeració. No pot ser una variable ni una expressió calculada.
  • break; — abandona el switch. Sense ell, l'execució continua cap al case següent.
  • default: — la branca de reserva. És opcional, però ometre-la sol ser un error de disseny: significa que no has pensat què fer amb els valors inesperats.

Exemple complet amb l'estat d'un exemplar de la biblioteca:

package com.nexussoftware.bibliotech;

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

        char codiEstat = 'P';   // D=disponible, P=prestat, R=reparacio, B=baixa

        switch (codiEstat) {
            case 'D':
                System.out.println("DISPONIBLE - es pot prestar");
                break;
            case 'P':
                System.out.println("PRESTAT - consultar data de devolucio");
                break;
            case 'R':
                System.out.println("EN REPARACIO - no disponible temporalment");
                break;
            case 'B':
                System.out.println("DE BAIXA - retirat del cataleg");
                break;
            default:
                System.out.println("CODI DESCONEGUT: " + codiEstat);
        }
    }
}

El flux, vist com a diagrama:

flowchart TD
    S["Avaluar codiEstat"] --> D1{"'D'"}
    D1 -- "coincideix" --> A1["DISPONIBLE"] --> F["Fi del switch"]
    D1 -- "no" --> D2{"'P'"}
    D2 -- "coincideix" --> A2["PRESTAT"] --> F
    D2 -- "no" --> D3{"'R'"}
    D3 -- "coincideix" --> A3["EN REPARACIO"] --> F
    D3 -- "no" --> D4{"'B'"}
    D4 -- "coincideix" --> A4["DE BAIXA"] --> F
    D4 -- "no" --> DEF["default: desconegut"] --> F

Detall pràctic: el default no ha d'anar per força al final (pot anar al mig i funciona igual), però posar-lo al final és la convenció universal i no hi ha cap raó per desviar-se'n. L'últim case o el default no necessiten break, perquè el switch acaba allà de totes maneres; així i tot, molts equips l'escriuen per si algú hi afegeix una branca després.

  1. El fall-through: per què existeix, quan s'aprofita i per què mossega

Fall-through (caiguda) significa que, en acabar les instruccions d'un case, l'execució continua al case següent en lloc de sortir del switch. No és un accident del llenguatge: és el seu comportament per disseny, heretat de C.

Observa què passa si s'oblida un break:

char codiEstat = 'D';

switch (codiEstat) {
    case 'D':
        System.out.println("DISPONIBLE");
        // falta el break!
    case 'P':
        System.out.println("PRESTAT");
        break;
    case 'R':
        System.out.println("EN REPARACIO");
        break;
}

Sortida:

DISPONIBLE
PRESTAT

El llibre es declara simultàniament disponible i prestat. El compilador no avisa, perquè el fall-through és legal. Aquest error és difícil de detectar llegint, perquè la vista salta de case en case i el break absent és invisible. Precisament per això és la fallada més reportada en revisions de codi sobre switch clàssic.

L'ús legítim: agrupar casos. Quan diversos valors comparteixen el mateix tractament, s'apilen les etiquetes sense instruccions entremig:

char codiEstat = 'R';

switch (codiEstat) {
    case 'D':
        System.out.println("L'exemplar ES POT prestar.");
        break;

    // Tres codis diferents, un unic tractament: fall-through intencionat
    case 'P':
    case 'R':
    case 'B':
        System.out.println("L'exemplar NO es pot prestar ara mateix.");
        break;

    default:
        System.out.println("Codi desconegut.");
}

Aquí case 'P': i case 'R': no tenen cos propi: cauen deliberadament fins al bloc de case 'B':. És idiomàtic i perfectament acceptable.

La regla professional, adoptada per totes les guies d'estil serioses: si un case amb instruccions cau al següent a propòsit, documenta-ho amb un comentari explícit:

case 'P':
    System.out.println("Registrant devolucio previa...");
    // fall-through intencionat: continua amb el tractament comu
case 'R':
    System.out.println("Exemplar no prestable.");
    break;

Sense aquest comentari, qui llegeixi el codi no podrà distingir la teva intenció d'un oblit. I qui el mantingui d'aquí a sis mesos probablement seràs tu.

  1. Tipus admesos en un switch

No tot pot anar dins dels parèntesis d'un switch. Els tipus permesos són:

Tipus Admès? Des de Nota
byte, short, char, int Java 1.0 Els enters petits; long no hi està permès
Byte, Short, Character, Integer Java 5 Embolcalls, amb autoboxing
enum Java 5 L'ús més natural del switch; el veuràs a la lliçó 04-07
String Java 7 Compara per contingut, com equals
long, float, double, boolean No Fes servir if-else
Qualsevol altre objecte Només amb pattern matching Java 21 Vegeu l'apartat 8

Dues precisions importants sobre String:

  1. La comparació és per contingut, equivalent a equals. No caus en el parany del == que vas veure a la lliçó 02-01.
  2. És sensible a majúscules: case "si" no captura "SI". La solució habitual és normalitzar abans: switch (resposta.toLowerCase()).
  3. Si la variable val null, un switch clàssic sobre String atura el programa amb NullPointerException. Protegeix-te amb un if abans, o normalitza amb trim() sobre un valor que sàpigues que no és nul.

Sobre long: l'exclusió sorprèn, però té una explicació històrica lligada a les instruccions de bytecode que implementen el switch. A la pràctica no és un problema: si necessites commutar sobre un long, o converteixes amb (int) quan sigui segur, o fas servir if-else.

  1. El switch amb fletxes de Java 14+

Java 14 va estabilitzar una sintaxi alternativa que resol el fall-through eliminant-lo del tot:

switch (codiEstat) {
    case 'D' -> System.out.println("DISPONIBLE - es pot prestar");
    case 'P' -> System.out.println("PRESTAT - consultar data de devolucio");
    case 'R' -> System.out.println("EN REPARACIO");
    case 'B' -> System.out.println("DE BAIXA");
    default  -> System.out.println("CODI DESCONEGUT: " + codiEstat);
}

Diferències respecte al clàssic, una per una:

  • Es fa servir -> en lloc de :.
  • No hi ha break, i no n'hi pot haver: cada branca és independent i en acabar surt del switch. El fall-through simplement no existeix.
  • Per agrupar valors se separen per comes a la mateixa etiqueta, en lloc d'apilar etiquetes:
switch (codiEstat) {
    case 'D'           -> System.out.println("Es pot prestar.");
    case 'P', 'R', 'B' -> System.out.println("No es pot prestar ara mateix.");
    default            -> System.out.println("Codi desconegut.");
}
  • Si una branca necessita diverses instruccions, s'embolcalla entre claus:
switch (opcio) {
    case "1" -> {
        System.out.println("=== CATALEG DE BIBLIOTECH ===");
        System.out.println("Java Eficac (978-0000000001)");
        System.out.println("Patrons de Disseny (978-0000000002)");
        System.out.println("Refactoritzacio (978-0000000003)");
    }
    case "0" -> System.out.println("Sortint...");
    default  -> System.out.println("Opcio no valida.");
}

Java 17, la versió de referència d'aquest curs, admet totes dues sintaxis. Per a codi nou, fes servir sempre la de fletxes: és més curta, més segura i elimina d'un cop la classe d'error més freqüent d'aquesta construcció. La sintaxi clàssica continua sent imprescindible per llegir codi existent, que és la meitat de la feina d'un desenvolupador.

  1. El switch com a expressió i la paraula clau yield

L'aportació més potent de Java 14 no és la fletxa, sinó que el switch pot ser una expressió: alguna cosa que produeix un valor i es pot assignar a una variable, exactament igual que l'operador ternari, però amb tantes branques com vulguis.

int diesRetard = 12;
final int LLINDAR_LLEU = 7;

// switch com a SENTENCIA: fa coses
switch (codiEstat) {
    case 'D' -> System.out.println("Disponible");
    default  -> System.out.println("No disponible");
}

// switch com a EXPRESSIÓ: produeix un valor (fixa't en l'"=" i en el ";" final)
String descripcio = switch (codiEstat) {
    case 'D' -> "DISPONIBLE";
    case 'P' -> "PRESTAT";
    case 'R' -> "EN REPARACIO";
    case 'B' -> "DE BAIXA";
    default  -> "DESCONEGUT";
};
System.out.println("Estat de l'exemplar: " + descripcio);

Tres regles governen el switch com a expressió:

  1. Ha de ser exhaustiu: per a qualsevol valor possible hi ha d'haver una branca. Amb char o String això obliga a incloure-hi default. Si falta, no compila (the switch expression does not cover all possible input values). Aquesta comprovació del compilador és or pur: fa impossible oblidar un cas.
  2. Acaba amb punt i coma després de la clau de tancament, perquè és part d'una sentència d'assignació.
  3. Totes les branques han de produir un valor del mateix tipus (o compatible).

yield: retornar un valor des d'un bloc. Quan una branca necessita diverses línies abans de produir el valor, es fan servir claus i l'última instrucció és yield:

final double TARIFA_DIARIA = 0.25;
final double MULTA_MAXIMA = 20.0;
String tipusUsuari = "EXTERN";
int diesRetard = 30;

double multa = switch (tipusUsuari) {
    case "EMPLEAT" -> diesRetard * TARIFA_DIARIA;

    case "BECARI" -> {
        // Els becaris paguen la meitat: calen diverses instruccions
        double base = diesRetard * TARIFA_DIARIA;
        double ambDescompte = base / 2;
        yield ambDescompte;                  // 'yield' entrega el valor de la branca
    }

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

    default -> 0.0;
};

System.out.printf("Multa per a %s: %.2f EUR%n", tipusUsuari, multa);
// Multa per a EXTERN: 15,00 EUR

yield és a un bloc de switch el que return és a un mètode: entrega el resultat. No el confonguis amb break, que en el switch clàssic només sortia sense retornar res.

També existeix la combinació de sintaxi clàssica amb expressió (case 'D': yield "DISPONIBLE";), però barrejar : i -> en el mateix switch és un error de compilació, i a la pràctica ningú no fa servir aquesta variant. Queda't amb les fletxes.

  1. Taula comparativa: clàssic, modern i escala if-else

Criteri switch clàssic switch amb fletxes Escala if-else
Sintaxi de branca case v: + break case v -> if (…) { }
Risc de fall-through Alt Cap No aplica
Agrupar valors Etiquetes apilades case a, b, c -> || a la condició
Pot retornar un valor No Sí (expressió) No (requereix ternari)
Exhaustivitat verificada No Sí, en expressions No
Rangs i condicions compostes No No
Diverses variables en la decisió No No
Llegibilitat amb 5+ branques Mitjana Alta Baixa
Disponible des de Java 1.0 Java 14 Java 1.0

Criteri d'elecció, en una frase: si compares una sola variable contra valors concrets, switch amb fletxes; si hi ha rangs, nuls o diverses variables, escala if-else.

  1. Pattern matching per a switch (avançament)

Java 21 va portar el switch un pas més enllà amb el pattern matching: les etiquetes case poden comprovar el tipus d'un objecte i extreure'n les dades en el mateix gest.

// Java 21+. Serveix de mostra: no ho facis servir encara.
String descripcio = switch (objecte) {
    case Integer i when i > 100 -> "Numero gran: " + i;
    case Integer i              -> "Numero: " + i;
    case String s               -> "Text de " + s.length() + " caracters";
    case null                   -> "Sense valor";
    default                     -> "Tipus no contemplat";
};

Fixa't en dues novetats: el case null (per fi el switch pot manejar nuls sense petar) i la clàusula when, que afegeix una condició extra a la coincidència.

Aquest codi requereix entendre classes, tipus de referència i jerarquies, que arriben als mòduls 3 i 4. S'estudia a la lliçó 10-06 (Java 9 i Més Enllà). N'hi ha prou que sàpigues que existeix i que en reconeguis la sintaxi si te la trobes.

  1. BiblioTech: el despatxador del menú

Amb tot l'anterior ja pots escriure la peça que estructurarà el projecte del mòdul: el bucle de menú de la lliçó 02-02 amb el seu despatx resolt per switch.

package com.nexussoftware.bibliotech;

import java.util.Scanner;

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

        final String NOM_EMPRESA = "Nexus Software";
        final int DIES_PRESTEC = 15;
        final double TARIFA_DIARIA = 0.25;
        final double MULTA_MAXIMA = 20.0;

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

        while (!sortir) {

            System.out.println();
            System.out.println("=== BIBLIOTECH - " + NOM_EMPRESA + " ===");
            System.out.println("  1. Consultar cataleg");
            System.out.println("  2. Registrar prestec");
            System.out.println("  3. Registrar devolucio");
            System.out.println("  0. Sortir");
            System.out.print("Opcio: ");

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

            switch (opcio) {

                case "1" -> {
                    System.out.println("--- CATALEG ---");
                    System.out.println("  Java Eficac          978-0000000001");
                    System.out.println("  Patrons de Disseny   978-0000000002");
                    System.out.println("  Refactoritzacio      978-0000000003");
                }

                case "2" -> {
                    System.out.print("  Empleat: ");
                    String empleat = scanner.nextLine().trim();
                    System.out.print("  Titol: ");
                    String titol = scanner.nextLine().trim();
                    System.out.printf("  PRESTEC registrat: %s -> %s (%d dies)%n",
                                      titol, empleat, DIES_PRESTEC);
                }

                case "3" -> {
                    System.out.print("  Dies transcorreguts: ");
                    int diesTranscorreguts = Integer.parseInt(scanner.nextLine().trim());

                    int diesRetard = diesTranscorreguts - DIES_PRESTEC;
                    if (diesRetard < 0) {
                        diesRetard = 0;
                    }

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

                    System.out.printf("  DEVOLUCIO: %d dies de retard, multa %.2f EUR%n",
                                      diesRetard, multa);
                }

                case "0" -> {
                    System.out.println("Tancant BiblioTech. Fins aviat.");
                    sortir = true;
                }

                default -> System.out.println("  Opcio no reconeguda. Fes servir 0, 1, 2 o 3.");
            }
        }

        scanner.close();
    }
}

Tres observacions sobre el disseny:

  1. L'opció es llegeix com a String i no com a int. Així, si l'usuari escriu hola, cau al default en lloc d'aturar el programa amb NumberFormatException. És una decisió defensiva barata i eficaç mentre no disposis d'excepcions (mòdul 6).
  2. La sortida es fa amb la bandera sortir, no amb break. I hi ha un motiu de pes, que és exactament el tema de la lliçó següent: dins d'un bucle, un break situat en un switch sortiria del switch, no del bucle, i això passa igual amb la sintaxi de fletxes (case "0" -> { ...; break; } compila i no tanca el menú). La bandera evita el problema d'arrel.
  3. Cada branca del switch va entre claus perquè conté diverses instruccions, i les variables declarades a dins (empleat, titol, diesRetard) només existeixen al seu bloc. Això evita col·lisions de noms entre branques.

Sessió d'exemple:

=== BIBLIOTECH - Nexus Software ===
  1. Consultar cataleg
  2. Registrar prestec
  3. Registrar devolucio
  0. Sortir
Opcio: 1
--- CATALEG ---
  Java Eficac          978-0000000001
  Patrons de Disseny   978-0000000002
  Refactoritzacio      978-0000000003

=== BIBLIOTECH - Nexus Software ===
...
Opcio: 3
  Dies transcorreguts: 27
  DEVOLUCIO: 12 dies de retard, multa 3,00 EUR

=== BIBLIOTECH - Nexus Software ===
...
Opcio: 9
  Opcio no reconeguda. Fes servir 0, 1, 2 o 3.

=== BIBLIOTECH - Nexus Software ===
...
Opcio: 0
Tancant BiblioTech. Fins aviat.

  1. BiblioTech: classificació textual de l'estat d'un préstec

El segon ús natural del switch a BiblioTech és traduir codis interns a text llegible. Aquí el switch com a expressió llueix especialment:

package com.nexussoftware.bibliotech;

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

        // Codi de gravetat calculat per les regles de negoci del modul 1:
        // 0 = sense retard, 1 = lleu, 2 = greu, 3 = multa amb sostre
        int gravetat = 2;
        String titol = "Patrons de Disseny";

        // Una sola variable, quatre valors discrets: cas de llibre per a switch.
        String estat = switch (gravetat) {
            case 0 -> "EN TERMINI";
            case 1, 2, 3 -> "AMB RETARD";      // tres codis, un mateix estat
            default -> "DESCONEGUT";
        };

        String missatge = switch (gravetat) {
            case 0 -> "Devolucio correcta. Gracies.";
            case 1 -> "Retard lleu. S'aplica la tarifa diaria.";
            case 2 -> "Retard greu. Revisa l'habit de devolucio.";
            case 3 -> {
                String avis = "Multa maxima assolida.";
                yield avis + " Es notificara a Recursos Humans.";
            }
            default -> "Codi de gravetat no contemplat: " + gravetat;
        };

        // La icona es un char: un altre switch expressio, en una linia per branca.
        char icona = switch (gravetat) {
            case 0 -> '+';
            case 1 -> '!';
            case 2, 3 -> 'X';
            default -> '?';
        };

        System.out.printf("[%c] %-22s %-12s %s%n", icona, titol, estat, missatge);
    }
}

Sortida:

[X] Patrons de Disseny     AMB RETARD   Retard greu. Revisa l'habit de devolucio.

Compara això amb l'alternativa del mòdul 1: tres cadenes de ternaris imbricats, il·legibles. El switch com a expressió permet assignar una variable amb diverses branques mantenint la claredat, i el compilador t'obliga a cobrir tots els casos.

Errors Habituals i Consells

1. Oblidar el break al switch clàssic. L'error estrella. S'executen branques que no volies i el compilador calla. Solució definitiva: fes servir la sintaxi de fletxes.

2. Posar break en un switch de fletxes. No cal mai per separar branques, i segons on l'escriguis passen tres coses diferents:

On Resultat
case "0" -> break; No compila: unexpected statement in case, expected is an expression, a block or a throw statement
case "0" -> { … break; } dins d'un switch de sentència Sí que compila, i surt del switch, no del bucle que l'envolta: el mateix parany del switch clàssic
break dins d'un switch d'expressió No compila: attempt to break out of a switch expression. Allà es fa servir yield

La conclusió pràctica és la mateixa en els tres casos: a la sintaxi de fletxes, no escriguis break.

3. Barrejar : i -> en el mateix switch. Error de compilació. Tria una forma i mantén-la a tot el bloc.

4. Fer servir una variable com a etiqueta de case.

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

Només valen literals i constants final inicialitzades amb literals. Si l'etiqueta s'ha de calcular, el teu problema no és de switch, sinó d'if-else.

5. Repetir un valor en dos case. case "1": ... case "1": és error de compilació (duplicate case label). És una bona notícia: el switch detecta duplicats que en una escala if-else passarien desapercebuts.

6. Oblidar el default en un switch d'expressió. No compila si el tipus no està cobert del tot. En un switch de sentència sí que compila, però deixa un forat silenciós: si arriba un valor inesperat, no passa absolutament res i ningú no se n'assabenta.

7. switch sobre String que pot ser null. Atura el programa. Valida abans:

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

8. Oblidar el punt i coma del switch com a expressió.

String estat = switch (gravetat) {
    case 0 -> "EN TERMINI";
    default -> "AMB RETARD";
}                       // <-- falta el ';' de l'assignacio

9. Consell: normalitza l'entrada abans de commutar. switch (resposta.trim().toLowerCase()) evita haver d'escriure case "SI", "Si", "si", "sI".

10. Consell: quan les branques creixin, se t'està demanant una altra cosa. Un switch de vint branques amb quinze línies cadascuna és senyal que cal descompondre en mètodes (lliçó 03-03) o, més endavant, fer servir polimorfisme (lliçó 03-06), que és la manera orientada a objectes d'eliminar switch gegants.

Exercicis

Exercici 1: caçar el fall-through oblidat

El codi següent pretén classificar el trimestre en què es va adquirir un llibre del catàleg de BiblioTech, però té dos break oblidats. Executa'l mentalment amb mesAdquisicio = 2 i amb mesAdquisicio = 5, anota'n la sortida real, identifica les dues fallades i corregeix-les. Després, reescriu la versió corregida fent servir switch amb fletxes.

int mesAdquisicio = 2;
String trimestre;

switch (mesAdquisicio) {
    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 VALID";
}

System.out.println("Adquirit al trimestre: " + trimestre);

Exercici 2: menú d'informes amb switch d'expressió

Escriu un programa que mostri aquest menú en bucle i faci servir un switch com a expressió per obtenir el text de l'informe seleccionat, i un switch de sentència a part per executar l'acció de sortir:

1. Informe de multes
2. Informe de prestecs actius
3. Informe de llibres mes retardats
0. Sortir

Requisits:

  • Les opcions 1, 2 i 3 han de produir una cadena descriptiva mitjançant un switch d'expressió assignat a una variable String informe.
  • L'opció 3 ha de fer servir un bloc amb yield, perquè construeix el seu text en dos passos.
  • Qualsevol altra entrada ha de caure al default i mostrar un avís sense trencar el bucle.
  • Compta quants informes s'han generat durant la sessió i mostra-ho en sortir.

Exercici 3: classificació combinada

Escriu un programa que, donat un int diesRetard, calculi primer un codi de gravetat (0 = sense retard, 1 = lleu si <= LLINDAR_LLEU, 2 = greu, 3 = multa amb sostre perquè l'import assoleix MULTA_MAXIMA) fent servir una escala if-else, i després faci servir un switch d'expressió sobre aquest codi per obtenir alhora l'etiqueta textual, l'import efectiu de la multa i el missatge per a l'empleat.

La clau de l'exercici és que vegis on acaba l'if i on comença el switch: els rangs els resol l'if; els valors discrets resultants els resol el switch. Prova-ho amb diesRetard igual a 0, 5, 30 i 200.

Solucions

Solució 1

Comportament del codi trencat.

Amb mesAdquisicio = 2: coincideix case 2, cau fins a case 3 (sense instruccions), assigna trimestre = "1T", i com que falta el break, continua caient per case 4 i case 5 fins a executar trimestre = "2T"; i allà sí que troba un break. Sortida: Adquirit al trimestre: 2T. Incorrecte: el febrer és 1T.

Amb mesAdquisicio = 5: coincideix case 5, assigna "2T", troba el break. Sortida: 2T. Correcte per casualitat.

I hi ha un tercer cas revelador, mesAdquisicio = 11: assigna "4T", cau al default i el sobreescriu amb "MES NO VALID". Sortida errònia també.

Els dos break oblidats són al final del bloc "1T" i al final del bloc "4T".

Versió clàssica corregida:

switch (mesAdquisicio) {
    case 1:
    case 2:
    case 3:
        trimestre = "1T";
        break;              // <-- AFEGIT
    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;              // <-- AFEGIT
    default:
        trimestre = "MES NO VALID";
}

Versió moderna, on l'error és impossible:

package com.nexussoftware.bibliotech;

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

        int mesAdquisicio = 2;

        // switch d'expressio: exhaustiu, sense break i sense fall-through possible.
        String trimestre = switch (mesAdquisicio) {
            case 1, 2, 3    -> "1T";
            case 4, 5, 6    -> "2T";
            case 7, 8, 9    -> "3T";
            case 10, 11, 12 -> "4T";
            default         -> "MES NO VALID";
        };

        System.out.println("Adquirit al trimestre: " + trimestre);   // 1T
    }
}

De vint-i-cinc línies a set, i amb una classe sencera d'errors eliminada. Aquesta és la raó per la qual el curs recomana la sintaxi de fletxes per a tot el codi nou.

Solució 2

package com.nexussoftware.bibliotech;

import java.util.Scanner;

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

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

        Scanner scanner = new Scanner(System.in);
        boolean sortir = false;
        int informesGenerats = 0;        // comptador de la sessio

        while (!sortir) {

            System.out.println();
            System.out.println("=== INFORMES BIBLIOTECH - " + NOM_EMPRESA + " ===");
            System.out.println("  1. Informe de multes");
            System.out.println("  2. Informe de prestecs actius");
            System.out.println("  3. Informe de llibres mes retardats");
            System.out.println("  0. Sortir");
            System.out.print("Opcio: ");

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

            // 'informe' val null quan l'opcio no genera informe (0 o invalida).
            String informe = switch (opcio) {

                case "1" -> "MULTES: 3 devolucions amb multa. "
                          + "Tarifa aplicada: " + TARIFA_DIARIA + " EUR/dia.";

                case "2" -> "PRESTECS ACTIUS: Marta Ruiz (Java Eficac), "
                          + "Diego Alonso (Patrons de Disseny).";

                case "3" -> {
                    // Branca de diverses instruccions: requereix claus i yield.
                    String pitjor = "Refactoritzacio";
                    int diesRetard = 105;
                    yield "MES RETARDATS: " + pitjor + " amb " + diesRetard + " dies.";
                }

                default -> null;    // 0 i qualsevol altra entrada
            };

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

            // Segon switch, ara com a SENTENCIA: executa accions.
            switch (opcio) {
                case "1", "2", "3" -> { /* ja s'ha impres a dalt */ }
                case "0" -> {
                    System.out.printf("Sessio tancada. Informes generats: %d%n",
                                      informesGenerats);
                    sortir = true;
                }
                default -> System.out.println("  Opcio no reconeguda.");
            }
        }

        scanner.close();
    }
}

Sessió d'exemple:

Opcio: 1
  MULTES: 3 devolucions amb multa. Tarifa aplicada: 0.25 EUR/dia.
Opcio: 3
  MES RETARDATS: Refactoritzacio amb 105 dies.
Opcio: 7
  Opcio no reconeguda.
Opcio: 0
Sessio tancada. Informes generats: 2

Nota sobre el disseny: fer servir null com a "no hi ha informe" funciona, però és exactament la classe de decisió que produeix NullPointerException en programes grans. Al mòdul 10 coneixeràs Optional, la forma moderna de representar "pot ser que no hi hagi valor". De moment, la guarda if (informe != null) compleix.

Solució 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 LLINDAR_LLEU = 7;

        int diesRetard = 30;
        String titol = "Refactoritzacio";
        String empleat = "Nuria Vidal";

        double multaTeorica = diesRetard * TARIFA_DIARIA;

        // ---- FASE 1: els RANGS els resol un if-else ----
        int gravetat;
        if (diesRetard == 0) {
            gravetat = 0;                          // sense retard
        } else if (multaTeorica >= MULTA_MAXIMA) {
            gravetat = 3;                          // multa amb sostre
        } else if (diesRetard <= LLINDAR_LLEU) {
            gravetat = 1;                          // lleu
        } else {
            gravetat = 2;                          // greu
        }

        // ---- FASE 2: els VALORS DISCRETS els resol un switch ----
        String etiqueta = switch (gravetat) {
            case 0 -> "EN TERMINI";
            case 1 -> "RETARD LLEU";
            case 2 -> "RETARD GREU";
            case 3 -> "MULTA MAXIMA";
            default -> "DESCONEGUT";
        };

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

        String missatge = switch (gravetat) {
            case 0 -> "Gracies per retornar a temps.";
            case 1 -> "Retard petit. Intenta ajustar-te al termini.";
            case 2 -> "Retard important. Revisa els teus prestecs actius.";
            case 3 -> {
                String base = "S'ha assolit el sostre de " + MULTA_MAXIMA + " EUR.";
                yield base + " Notificacio enviada a Recursos Humans.";
            }
            default -> "Sense missatge.";
        };

        System.out.println("=== AVALUACIO DE DEVOLUCIO ===");
        System.out.printf("%-14s %s%n",       "Empleat:", empleat);
        System.out.printf("%-14s %s%n",       "Llibre:", titol);
        System.out.printf("%-14s %d%n",       "Dies retard:", diesRetard);
        System.out.printf("%-14s %s%n",       "Classificacio:", etiqueta);
        System.out.printf("%-14s %.2f EUR%n", "Multa:", multaEfectiva);
        System.out.printf("%-14s %s%n",       "Missatge:", missatge);
    }
}

Resultats amb els quatre valors demanats:

diesRetard Multa teòrica gravetat Etiqueta Multa efectiva
0 0,00 € 0 EN TERMINI 0,00 €
5 1,25 € 1 RETARD LLEU 1,25 €
30 7,50 € 2 RETARD GREU 7,50 €
200 50,00 € 3 MULTA MAXIMA 20,00 €

La lliçó de l'exercici és el repartiment de responsabilitats: if-else tradueix rangs continus en un codi discret, i a partir d'aquí el switch pot fer la seva feina. Fixa't també que l'ordre de l'if-else importa: la comprovació del sostre va abans que la de LLINDAR_LLEU, perquè una multa amb sostre és sempre prioritària sobre la classificació per dies. I observa que tres switch diferents comparteixen el mateix codi de gravetat; quan arribis a la lliçó 04-07 veuràs que les enumeracions són la manera correcta de representar aquest codi, i el compilador fins i tot podrà verificar que no falta cap branca sense necessitat de default.

Conclusió

Ja saps despatxar decisions sobre valors discrets. Has vist el switch clàssic amb case, break i default; el fall-through, la seva raó de ser, el seu ús legítim per agrupar casos i per què un break oblidat és l'error més freqüent d'aquesta construcció; els tipus admesos (int i els seus germans petits, char, String, enum) i els prohibits (long, double, boolean); el switch amb fletxes de Java 14+, on el fall-through és impossible i agrupar valors és qüestió de comes; i el switch com a expressió, amb yield per a les branques de diverses línies i amb l'exhaustivitat verificada pel compilador. Amb la taula comparativa tens el criteri: switch per a valors concrets d'una variable, if-else per a rangs i condicions compostes.

BiblioTechApp ja té un menú en bucle amb el seu despatxador escrit amb fletxes, quatre opcions operatives i una branca default que absorbeix les entrades errònies sense trencar res. També has vist que la sortida es controla amb una bandera booleana, i se n'ha avançat el motiu: un break dins d'un switch no fa el que un principiant espera.

Precisament això és el que aclareix la lliçó següent, Break i continue: com abandonar un bucle tan bon punt trobes el que buscaves, com saltar una iteració que no interessa, per què el break d'un switch imbricat en un bucle és un parany clàssic, i quines alternatives —etiquetes, banderes— existeixen quan necessites trencar diversos nivells alhora.

Curs de Programació en Java

Mòdul 1: Introducció a Java

Mòdul 2: Flux de control

Mòdul 3: Programació orientada a objectes

Mòdul 4: Programació orientada a objectes avançada

Mòdul 5: Estructures de dades i col·leccions

Mòdul 6: Gestió d'excepcions

Mòdul 7: Entrada/sortida de fitxers

Mòdul 8: Multifil i concurrència

Mòdul 9: Xarxes

Mòdul 10: Temes avançats

Mòdul 11: Frameworks i llibreries de Java

Mòdul 12: Construcció d'aplicacions del món real

© Copyright 2026. Tots els drets reservats