Istruzioni case con freccia in Java
- Le istruzioni case con freccia in Java offrono un modo più conciso e leggibile per gestire le condizioni nei blocchi switch.
- Queste istruzioni eliminano la necessità di utilizzare l'istruzione break, riducendo il rischio di errori di fall-through.
- Le istruzioni case con freccia possono essere utilizzate sia in espressioni switch che in istruzioni switch, offrendo flessibilità nel codice.
- L'uso di blocchi di codice con freccia consente di eseguire più operazioni all'interno di un singolo case, migliorando la chiarezza del codice.
L'uso della freccia nei case
Sebbene l'uso di yield
sia un modo perfettamente valido per specificare un valore per un'espressione switch
, non è l'unico modo per farlo. In molte situazioni, un modo più semplice per fornire un valore è attraverso l'uso di una nuova forma del case
che sostituisce ->
(una freccia) ai due punti in un case
.
Per esempio, questa riga:
case 'X': // ...
può essere riscritta usando la freccia così:
case 'X' -> // ...
Per evitare confusione, in questa discussione ci riferiremo a un case
con una freccia come caso freccia e alla forma tradizionale basata sui due punti come caso due punti. Sebbene entrambe le forme corrisponderanno al carattere X
, l'azione precisa di ogni stile di istruzione case
differisce in tre modi molto importanti:
-
Primo, un
case
freccia non continua alcase
successivo. Quindi, non c'è bisogno di usarebreak
.L'esecuzione semplicemente termina alla fine di un
case
freccia. Sebbene la natura di continuazione di uncase
tradizionale basato sui due punti sia sempre stata parte di Java, la continuazione è stata criticata perché può essere una fonte di bug, come quando il programmatore dimentica di aggiungere un'istruzionebreak
per prevenire la continuazione quando la continuazione non è desiderata. Il case freccia evita questa situazione. -
Secondo, il
case
freccia fornisce un modo "abbreviato" per fornire un valore quando usato in un'espressioneswitch
. Per questa ragione, ilcase
freccia è spesso usato nelle espressioniswitch
. -
Terzo, il target di un
case
freccia deve essere un'espressione oppure un blocco oppure lanciare un'eccezione.Non può essere una sequenza di istruzioni, come è consentito con un
case
tradizionale. Quindi, il caso freccia avrà una di queste forme generali:case costante -> espressione; case costante -> { blocco-di-istruzioni } case costante -> throw eccezione;
Naturalmente, le prime due forme rappresentano gli usi primari.
Probabilmente, l'uso più comune di un case
freccia è in un'espressione switch
, e il target più comune del case
freccia è un'espressione. Quindi, è qui che inizieremo. Quando il target di un case
freccia è un'espressione, il valore di quell'espressione diventa il valore dell'espressione switch
quando quel case
viene abbinato. Quindi, fornisce un'alternativa molto efficiente all'istruzione yield
in molte situazioni. Per esempio, ecco il primo case
nell'esempio delle lezioni precedenti riscritto per usare un case
freccia:
case 1000, 1205, 8900 -> 1;
Qui, il valore dell'espressione (che è 1) diventa automaticamente il valore prodotto dallo switch
quando questo case
viene abbinato. In altre parole, l'espressione diventa il valore prodotto dallo switch
. Si noti che questa istruzione è piuttosto compatta, ma esprime chiaramente l'intento di fornire un valore.
Proviamo a riscrivere completamente il programma delle lezioni precedenti usando un case
freccia. Per farlo, dobbiamo riscrivere ogni case
per usare un case
freccia e rimuovere le istruzioni break
. Inoltre, poiché non c'è bisogno di un'istruzione yield
quando si usa un case
freccia, possiamo rimuovere anche quelle. Ecco il programma completo:
// Usa una switch expression con case freccia
class SwitchFreccia {
public static void main(String[] args) {
// Inizializza il codice evento
int codiceEvento = 6010;
// Usa una switch expression per determinare il livello di priorità
int livelloPriorita = switch (codiceEvento) {
case 1000, 1205, 8900 -> 1;
case 2000, 6010, 9128 -> 2;
case 1002, 7023, 9300 -> 3;
default -> 0;
};
// Stampa il livello di priorità
System.out.println("Livello di priorità per il codice evento " +
codiceEvento +
" è " + livelloPriorita);
}
}
Questo produce lo stesso output di prima. Guardando il codice, è facile vedere perché questa forma del case
freccia è così appropriata per molti tipi di espressioni switch
. È compatta ed elimina la necessità di un'istruzione yield
separata. Poiché il case
freccia non continua, non c'è bisogno di un'istruzione break
. Ogni case
termina producendo il valore della sua espressione. Inoltre, se confrontiamo questa versione finale dello switch
con lo switch
tradizionale originale mostrato all'inizio di questa discussione, è facilmente evidente quanto sia snella ed espressiva questa versione. In combinazione, i miglioramenti dello switch
offrono un modo davvero impressionante per migliorare la chiarezza e la resilienza del codice.
Approfondimenti sul case
con freccia
Il case
con freccia fornisce una flessibilità considerevole. Prima di tutto, quando si usa la sua forma di espressione, l'espressione può essere di qualsiasi tipo. Per esempio, la seguente è un'istruzione case
valida:
case -1 –> ottieniCodiceErrore();
Qui, il risultato della chiamata a ottieniCodiceErrore()
diventa il valore dell'espressione switch
che la racchiude. Ecco un altro esempio:
case 0 -> completamentoNormale = true;
In questo caso, il risultato dell'assegnazione, che è true
, diventa il valore prodotto. Il punto chiave è che qualsiasi espressione valida può essere usata come target del case
con freccia purché sia compatibile con il tipo richiesto dal switch
.
Come menzionato, il target del ->
può anche essere un blocco di codice. Bisognerà usare un blocco come target di un case
con freccia ogni volta che si ha bisogno di più di una singola espressione. Per esempio, ogni case
in questa versione del programma del codice evento imposta il valore di una variabile boolean
chiamata fermatiOra
per indicare se è richiesta una terminazione immediata e poi produce il livello di priorità.
// Usa case con freccia e blocchi
class CaseFrecciaBlocco {
public static void main(String[] args) {
// Flag che indica se il processo deve fermarsi immediatamente
boolean fermatiOra;
int codiceEvento = 9300;
// Usa blocchi di codice con una freccia. Ancora, si noti
// che nessuna istruzione break è richiesta (o permessa)
// per prevenire il fall through. Poiché il target di una
// freccia è un blocco, yield deve essere usato per fornire un valore.
int livelloPriorita = switch (codiceEvento) {
case 1000, 1205, 8900 -> {
fermatiOra = false;
System.out.println("Allerta");
yield 1;
}
case 2000, 6010, 9128 -> {
fermatiOra = false;
System.out.println("Avviso");
yield 2;
}
case 1002, 7023, 9300 -> {
fermatiOra = true;
System.out.println("Pericolo");
yield 3;
}
default -> {
fermatiOra = false;
System.out.println("Normale.");
yield 0;
}
};
System.out.println("Livello di priorità per il codice evento " + codiceEvento +
" è " + livelloPriorita);
if (fermatiOra) {
System.out.println("Stop richiesto.");
}
}
}
Di seguito è riportato l'output di questo programma:
Pericolo
Livello di priorità per il codice evento 9300 è 3
Stop richiesto.
Come mostra questo esempio, quando si usa un blocco, si deve usare yield
per fornire un valore a un'espressione switch
. Inoltre, anche se vengono usati target a blocco, ogni percorso attraverso l'espressione switch
deve ancora fornire un valore.
Anche se il programma precedente fornisce una semplice illustrazione di un target a blocco di un case
con freccia, solleva anche una domanda interessante. Si noti che ogni case
nello switch
imposta il valore di due variabili. La prima è livelloPriorita
, che è il valore prodotto. La seconda è fermatiOra
. C'è un modo per un'espressione switch
di produrre più di un valore? In senso diretto, la risposta è "no" perché solo un valore può essere prodotto dallo switch
. Tuttavia, è possibile incapsulare due o più valori all'interno di una classe e produrre un oggetto di quella classe.
A partire dal JDK 16, Java fornisce un modo particolarmente snello ed efficiente per compiere questo: i record. Studieremo in dettaglio i record nelle prossime lezioni. Per il momento basta sapere che un record aggrega due o più valori in una singola unità logica. Come si relaziona a questo esempio, un record potrebbe contenere sia i valori livelloPriorita
che fermatiOra
, e questo record potrebbe essere prodotto dallo switch
come un'unità. Quindi, un record offre un modo conveniente per uno switch
di produrre più di un singolo valore.
Anche se il case
con freccia è molto utile in un'espressione switch
, è importante enfatizzare che non è limitato a quell'uso. Il case
con freccia può anche essere usato in un'istruzione switch
, che permette di scrivere switch
in cui non può verificarsi alcun fall-through del case
. In questa situazione, nessuna istruzione yield
è richiesta (o permessa), e nessun valore è prodotto dallo switch
. In sostanza, funziona molto come uno switch
tradizionale ma senza il fall-through. Ecco un esempio:
// Usa case frecce con un'istruzione switch
class IstruzioneSwitchConFrecce {
public static void main(String[] args) {
int su = 0;
int giù = 0;
int sinistra = 0;
int destra = 0;
char direzione = 'R';
// Usa case frecce con un'istruzione switch. Nota che
// nessun valore è prodotto.
switch (direzione) {
case 'L' -> {
System.out.println("Gira a Sinistra");
sinistra++;
}
case 'R' -> {
System.out.println("Gira a Destra");
destra++;
}
case 'U' -> {
System.out.println("Muove Su");
su++;
}
case 'D' -> {
System.out.println("Muove Giù");
giù++;
}
}
System.out.println(destra);
}
}
In questo programma, lo switch
è un'istruzione, non un'espressione. Questo è dovuto a due ragioni. Prima, nessun valore è prodotto. Secondo, non è esaustivo perché nessuna istruzione default
è inclusa. (Si ricordi che le espressioni switch
devono essere esaustive, ma non le istruzioni switch
.) Si noti, tuttavia, che poiché non si verifica fall-through con un case
con freccia, nessuna istruzione break
è necessaria. Come punto di interesse, poiché ogni case
incrementa il valore di una variabile diversa, non sarebbe possibile trasformare questo switch
in un'espressione. Quale valore produrrebbe? Tutti e quattro i case
incrementano una variabile diversa.
Un ultimo punto: non si può mescolare case
con freccia con case
tradizionali con due punti nello stesso switch
. Bisogna scegliere uno o l'altro. Per esempio, questa sequenza non è valida:
// ERRORE
// Questo codice non compila perché mescola stili di case.
switch(direzione) {
case 'L' -> {
System.out.println("Girando a Sinistra");
sinistra++;
}
case 'R' : // Sbagliato!
System.out.println("Girando a Destra");
destra++;
break;
case 'U' -> {
System.out.println("Muovendosi Su");
su++;
}
case 'D' -> {
System.out.println("Muovendosi Giù");
giù++;
}
}
Un altro esempio di espressione switch
Per concludere questa panoramica sui miglioramenti di switch
, viene presentato un altro esempio. Utilizza un'espressione switch
per determinare se una lettera è una vocale della lingua inglese. Fa uso di tutte le nuove funzionalità di switch
. Si presti particolare attenzione al modo in cui viene gestita la Y. In inglese, Y può essere una vocale o una consonante. Il programma permette di specificare in che modo si vuole interpretare la Y attraverso il modo in cui è impostata la variabile yEVocale
. Per gestire questo caso speciale, viene utilizzato un blocco come destinazione della freccia ->
.
// Usa un'espressione switch per determinare
// se un carattere è una vocale inglese.
// Si noti l'uso di un blocco come destinazione di un caso freccia per Y.
class Vocali {
public static void main(String[] args) {
// Questo flag indica se Y è considerata una vocale.
// Se è impostato su true, Y è una vocale.
// Se è impostato su false, Y è una consonante.
boolean yVocale = true;
char ch = 'Y';
boolean vocale = switch(ch) {
case 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U' -> true;
case 'y', 'Y' -> {
if(yVocale)
yield true;
else
yield false;
}
default -> false;
};
if(vocale) {
System.out.println(ch + " è una vocale.");
}
}
}
Come esperimento, si provi a riscrivere questo programma utilizzando uno switch
tradizionale. Come si vedrà, si otterrà una versione molto più lunga e meno gestibile. I nuovi miglioramenti di switch
spesso forniscono un approccio superiore.