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.
| Caratteristica | switch tradizionale (case L:) | switch moderno (case L ->) |
|---|---|---|
| Fall-through | Sì, a meno che non usi break | No, ogni ramo è isolato |
| Produce un valore | No | Sì (è un'espressione) |
| Etichette multiple | Righe case vuote impilate | Separate da virgola su una riga |
| Visibilità delle variabili | Condivisa in tutto il blocco | Locale 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();
};
}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.
Cosa ricavare dall'esecuzione:
kind()restituisce il risultato di un'espressione switch direttamente —MONDAYeFRIDAYstampanoweekday,SATURDAYstampaweekend, il tutto senza un singolobreak.- I due rami freccia in
kind()coprono tutte e sette le costanti dell'enum, quindi lo switch è esaustivo e non ha bisogno didefault. - 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 conyield— mostrando come un ramo a blocco possa fare lavoro prima di produrre il suo valore. - Il
switchfinale assegnaTWOdirettamente nella variabilelabel, 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
enumo i sottotipi di un tipo sealed.
Per la storia completa e la forma con etichette a due punti, vedi espressioni switch Java.