W3docs

Espressioni switch moderne in Java

Usa le espressioni switch moderne in Java con etichette freccia, yield, esaustività e pattern matching.

Il tradizionale istruzione switch fa parte di Java dalla versione 1.0, ma portava con sé molti problemi: bug di fall-through, istruzioni break ripetitive e nessun modo per produrre un valore. Le espressioni switch, finalizzate in Java 14, risolvono tutto ciò. Trasformano switch da un'ingombrante istruzione di controllo del flusso in un'espressione concisa che produce un valore.

Questo capitolo illustra il switch moderno: etichette freccia, la parola chiave yield, casi con etichette multiple, verifica dell'esaustività e come la nuova forma differisce dall'istruzione che potresti già conoscere.

Da istruzione a espressione

La classica istruzione switch esegue effetti collaterali e si affida a break per interrompere il fall-through. Se dimentichi un break, l'esecuzione cade silenziosamente nel caso successivo — una fonte notoria di bug.

// Traditional switch statement (error-prone)
String kind;
switch (day) {
    case SATURDAY:
    case SUNDAY:
        kind = "weekend";
        break;          // forget this and you fall through
    default:
        kind = "weekday";
}

Un'espressione switch riduce tutto ciò a un singolo assegnamento. La forma con freccia (->) non ha mai fall-through, quindi non è necessario alcun break.

// Modern switch expression
String kind = switch (day) {
    case SATURDAY, SUNDAY -> "weekend";
    default               -> "weekday";
};

L'intero switch ora restituisce un valore, che puoi assegnare, restituire o passare direttamente come argomento.

Etichette freccia e casi con etichette multiple

L'etichetta freccia case L -> associa un'etichetta (o più, separate da virgola) a una singola azione. Viene eseguito solo il ramo corrispondente — non c'è fall-through di cui preoccuparsi.

int numLetters = switch (month) {
    case JANUARY, JUNE, JULY          -> 4;
    case FEBRUARY, MARCH, APRIL, MAY  -> 5;
    case SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER -> switchOnLength(month);
    case AUGUST                       -> 6;
};

Raggruppare le etichette con virgole sostituisce il vecchio trucco di impilare righe case vuote per condividere un corpo, ed è molto più leggibile.

Caratteristicaswitch tradizionale (case L:)switch moderno (case L ->)
Fall-throughSì, a meno che non usi breakNo, ogni ramo è isolato
Produce un valoreNoSì (è un'espressione)
Etichette multipleRighe case vuote impilateSeparate da virgola su una riga
Visibilità delle variabiliCondivisa in tutto il bloccoLocale a ciascun blocco ramo

Blocchi e la parola chiave yield

Quando un ramo ha bisogno di più di una singola espressione, usa un blocco { ... } e restituisci il suo valore con yield. La parola chiave yield è a un'espressione switch ciò che return è a un metodo: fornisce il valore prodotto dal ramo.

int gradePoints = switch (grade) {
    case 'A' -> 4;
    case 'B' -> 3;
    default -> {
        log("Unknown grade: " + grade);
        yield 0;          // the value this branch evaluates to
    }
};

Puoi ancora usare etichette con i due punti con yield se preferisci la vecchia sintassi, ma la forma con freccia è la scelta moderna idiomatica e evita completamente il fall-through accidentale.

Esaustività e default

Un'espressione switch deve essere esaustiva: ogni possibile input deve essere gestito, perché l'espressione deve produrre un valore in ogni caso. Per la maggior parte dei tipi soddisfi questo requisito con un ramo default. Per un enum, il compilatore può verificare l'esaustività direttamente — se copri ogni costante, default diventa facoltativo.

// No default needed: all enum constants are covered,
// so the compiler knows the switch is exhaustive.
boolean isWeekend = switch (day) {
    case SATURDAY, SUNDAY -> true;
    case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> false;
};

Se ometti una costante e non c'è default, il codice non verrà compilato. Questa rete di sicurezza in fase di compilazione è uno dei maggiori vantaggi pratici rispetto alla vecchia istruzione, che lasciava silenziosamente una variabile non assegnata.

Pattern matching in switch

Le versioni recenti di Java estendono switch in modo che ogni case possa corrispondere al tipo del valore, non solo a una costante. Questo è il pattern matching per switch (in anteprima in Java 17–20, finalizzato in Java 21). Invece di scrivere una catena di controlli instanceof e cast, etichetti ogni ramo con un pattern di tipo e associ una variabile nello stesso passaggio.

static String describe(Object obj) {
    return switch (obj) {
        case Integer i -> "int " + i;            // matches and binds i
        case String s  -> "string of length " + s.length();
        case null      -> "nothing";             // null can be its own label
        default        -> "something else";
    };
}

Il pattern matching è particolarmente potente con record e tipi sealed: quando uno switch copre ogni sottotipo permesso di un tipo sealed, il compilatore lo considera esaustivo, quindi puoi eliminare completamente default.

sealed interface Shape permits Circle, Square {}
record Circle(double radius) implements Shape {}
record Square(double side)   implements Shape {}

static double area(Shape shape) {
    return switch (shape) {          // no default: all permitted types covered
        case Circle c -> Math.PI * c.radius() * c.radius();
        case Square s -> s.side() * s.side();
    };
}
Nota
Prima del pattern matching, uno switch poteva testare solo un enum, un tipo integrale o una String. I pattern di tipo permettono a switch di lavorare su qualsiasi tipo riferimento, rendendolo un sostituto naturale per le lunghe catene if/else if.

Un esempio completo

Il programma seguente mette insieme tutti gli elementi: etichette freccia con casi multi-etichetta, un blocco yield per un ramo calcolato, copertura esaustiva di enum e un'espressione switch assegnata direttamente a una variabile.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • kind() restituisce il risultato di un'espressione switch direttamente — MONDAY e FRIDAY stampano weekday, SATURDAY stampa weekend, il tutto senza un singolo break.
  • I due rami freccia in kind() coprono tutte e sette le costanti dell'enum, quindi lo switch è esaustivo e non ha bisogno di default.
  • In letterGrade(), i punteggi 95, 83, 71 e 64 vengono mappati chiaramente tramite etichette freccia a 4, 3, 2 e 1 punti di voto.
  • Il punteggio 42 raggiunge il blocco default, che prima stampa (failing score 42) tramite la riga con effetto collaterale e poi restituisce 0 con yield — mostrando come un ramo a blocco possa fare lavoro prima di produrre il suo valore.
  • Il switch finale assegna TWO direttamente nella variabile label, dimostrando che un'espressione switch è un valore che puoi memorizzare, non solo controllo del flusso.

Quando usare quale forma

  • Opta per un'espressione switch (case L ->) ogni volta che l'obiettivo è calcolare e restituire un singolo valore. È esaustiva, priva di fall-through e si legge come un singolo assegnamento.
  • Una tradizionale istruzione switch ha ancora senso quando ogni ramo è puramente un effetto collaterale (log, dispatch) e non c'è nessun valore da produrre.
  • Usa i pattern di tipo quando stai ramificando sul tipo runtime di un oggetto, in particolare tra le costanti di un enum o i sottotipi di un tipo sealed.

Per la storia completa e la forma con etichette a due punti, vedi espressioni switch Java.

Esercitazione

Pratica
In un'espressione switch moderna, quale parola chiave produce un valore dall'interno di un blocco (la forma { ... } di un ramo)?
In un'espressione switch moderna, quale parola chiave produce un valore dall'interno di un blocco (la forma { ... } di un ramo)?
Was this page helpful?