Istruzione switch in Java

Concetti Chiave
  • L'istruzione switch in Java consente di indirizzare l'esecuzione a parti diverse del codice in base al valore di un'espressione.
  • La forma tradizionale di switch è ampiamente utilizzata e funziona in tutti gli ambienti di sviluppo Java.
  • L'istruzione switch può essere utilizzata con tipi primitivi come int, char, byte, short, e ora anche con String.
  • L'istruzione switch può essere annidata e le istruzioni case possono essere raggruppate senza l'uso di break per eseguire lo stesso blocco di codice.

Istruzione switch

L'istruzione switch è l'istruzione condizionale multipla di Java.

Fornisce un modo semplice per indirizzare l'esecuzione a parti diverse del codice in base al valore di un'espressione. In tal modo, spesso rappresenta un'alternativa migliore rispetto a una lunga serie di istruzioni if-else-if.

All'inizio è opportuno precisare che, a partire da JDK 14, lo switch è stato notevolmente potenziato ed esteso con diverse nuove funzionalità che vanno ben oltre la sua forma tradizionale.

La forma tradizionale di switch è presente in Java fin dall'inizio ed è quindi ampiamente utilizzata. Inoltre, è la forma che funziona in tutti gli ambienti di sviluppo Java e per tutti le versioni del JDK.

A causa della rilevanza delle recenti estensioni dello switch, tali miglioramenti sono descritti nelle lezioni future, nel contesto di altre aggiunte recenti a Java. Qui viene esaminata la forma tradizionale dello switch.

Di seguito è mostrata la forma generale di un'istruzione switch tradizionale:

switch (espressione) {
    case valore1:
        // sequenza di istruzioni
        break;
    case valore2:
        // sequenza di istruzioni
        break;
    .
    .
    .
    case valoreN:
        // sequenza di istruzioni
        break;
    default:
        // sequenza di istruzioni di default
}

Per le versioni di Java precedenti a JDK 7, espressione deve risolvere a un tipo byte, short, int, char o a un'enumerazione (vedremo le enumerazioni più avanti).

Oggi, espressione può anche essere di tipo String. Ogni valore specificato nelle istruzioni case deve essere un'espressione costante univoca (ad esempio un valore letterale). I valori duplicati nei case non sono consentiti. Il tipo di ogni valore deve essere compatibile con il tipo di espressione.

L'istruzione switch tradizionale funziona in questo modo: il valore di espressione viene confrontato con ciascuno dei valori nelle istruzioni case. Se viene trovato un riscontro, viene eseguita la sequenza di codice che segue tale case. Se nessuno dei valori corrisponde al valore di espressione, viene eseguita l'istruzione default. Tuttavia, default è facoltativa. Se non vi sono corrispondenze e non è presente default, non viene intrapresa alcuna azione.

L'istruzione break viene utilizzata all'interno dello switch per terminare una sequenza di istruzioni. Quando si incontra un break, l'esecuzione passa alla prima riga di codice che segue l'intero switch. Ciò ha l'effetto di “saltare fuori” dallo switch.

Ecco un semplice esempio che utilizza un'istruzione switch:

// Un semplice esempio dello switch.
class EsempioSwitch {
    public static void main(String[] args) {
        for (int indice = 0; indice < 6; indice++)
            switch (indice) {
                case 0:
                    System.out.println("indice è zero.");
                    break;
                case 1:
                    System.out.println("indice è uno.");
                    break;
                case 2:
                    System.out.println("indice è due.");
                    break;
                case 3:
                    System.out.println("indice è tre.");
                    break;
                default:
                    System.out.println("indice è maggiore di 3.");
            }
    }
}

L'output prodotto da questo programma è il seguente:

indice è zero.
indice è uno.
indice è due.
indice è tre.
indice è maggiore di 3.
indice è maggiore di 3.

Come si può osservare, a ogni iterazione del ciclo vengono eseguite le istruzioni associate alla costante case che corrisponde a indice. Tutte le altre vengono saltate. Quando indice è maggiore di 3, nessuna istruzione case corrisponde, quindi viene eseguito default.

L'istruzione break è facoltativa. Se viene omessa, l'esecuzione continua nel case successivo. Talvolta è utile avere più case senza istruzioni break intermedie. Ad esempio, si consideri il programma seguente:

// In uno switch, le istruzioni break sono facoltative.
class BreakMancante {
    public static void main(String[] args) {
        for (int indice = 0; indice < 12; indice++)
            switch (indice) {
                case 0:
                case 1:
                case 2:
                case 3:
                case 4:
                    System.out.println("indice è minore di 5");
                    break;
                case 5:
                case 6:
                case 7:
                case 8:
                case 9:
                    System.out.println("indice è minore di 10");
                    break;
                default:
                    System.out.println("indice è 10 o più");
            }
    }
}

Questo programma genera il seguente output:

indice è minore di 5
indice è minore di 5
indice è minore di 5
indice è minore di 5
indice è minore di 5
indice è minore di 10
indice è minore di 10
indice è minore di 10
indice è minore di 10
indice è minore di 10
indice è 10 o più
indice è 10 o più

Si nota come l'esecuzione cada attraverso ogni case finché non viene raggiunta un'istruzione break (o la fine dello switch).

Sebbene l'esempio precedente sia ovviamente costruito a scopo illustrativo, l'omissione di break ha molte applicazioni pratiche nei programmi reali. Un esempio più realistico è la seguente riscrittura del programma sulle stagioni visto in precedenza, che utilizza uno switch per un'implementazione più efficiente:

// Una versione migliorata del programma sulle stagioni.
class Interruttore {
    public static void main(String[] args) {
        int mese = 4;
        String stagione;

        switch (mese) {
            case 12:
            case 1:
            case 2:
                stagione = "Inverno";
                break;
            case 3:
            case 4:
            case 5:
                stagione = "Primavera";
                break;
            case 6:
            case 7:
            case 8:
                stagione = "Estate";
                break;
            case 9:
            case 10:
            case 11:
                stagione = "Autunno";
                break;
            default:
                stagione = "Mese non valido";
        }
        System.out.println("Aprile è in " + stagione + ".");
    }
}

È anche possibile utilizzare una stringa per controllare un'istruzione switch. Ad esempio:

// Utilizzare una stringa per controllare uno switch.

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

        String stringa = "due";

        switch (stringa) {
            case "uno":
                System.out.println("uno");
                break;
            case "due":
                System.out.println("due");
                break;
            case "tre":
                System.out.println("tre");
                break;
            default:
                System.out.println("nessuna corrispondenza");
                break;
        }
    }
}

Come previsto, l'output del programma è

due

La stringa contenuta in stringa (che in questo programma è “due”) viene confrontata con le costanti case. Quando si trova una corrispondenza (come accade nel secondo case), viene eseguita la sequenza di codice associata.

La possibilità di utilizzare stringhe in un'istruzione switch semplifica molte situazioni; ad esempio, uno switch basato su stringhe rappresenta un miglioramento rispetto a una sequenza equivalente di istruzioni if/else. Tuttavia, lo switch su stringhe può essere più costoso rispetto allo switch su interi. Pertanto è consigliabile utilizzare lo switch su stringhe solo quando i dati di controllo sono già in forma di stringa. In altre parole, evitare l'uso di stringhe in uno switch quando non è necessario.

Istruzioni switch annidate

È possibile usare uno switch come parte della sequenza di istruzioni di uno switch esterno. Questo è chiamato switch annidato. Poiché un'istruzione switch definisce il proprio blocco, non sorgono conflitti tra le costanti case nello switch interno e quelle nello switch esterno. Ad esempio, il seguente frammento è perfettamente valido:

switch (conteggio) {
    case 1:
        switch (bersaglio) { // switch annidato
            case 0:
                System.out.println("bersaglio è zero");
                break;
            case 1: // nessun conflitto con lo switch esterno
                System.out.println("bersaglio è uno");
                break;
        }
        break;
    case 2: // ...
}

In questo caso, l'istruzione case 1: nello switch interno non confligge con l'istruzione case 1: nello switch esterno. La variabile conteggio viene confrontata solo con l'elenco dei case al livello esterno. Se conteggio vale 1, allora bersaglio viene confrontato con i case dell'elenco interno.

In sintesi, vi sono tre caratteristiche importanti dello switch da notare:

  • Lo switch differisce dall'if in quanto switch può testare solo l'uguaglianza, mentre if può valutare qualsiasi tipo di espressione booleana. Vale a dire, lo switch verifica solo la corrispondenza tra il valore dell'espressione e una delle sue costanti case.
  • Nessuna delle costanti case nello stesso switch può avere valori identici. Naturalmente, un'istruzione switch e uno switch esterno che la racchiude possono avere costanti case in comune.
  • Un'istruzione switch è di solito più efficiente di un insieme di if annidati.

L'ultimo punto è particolarmente interessante perché offre un'idea di come funziona il compilatore Java. Quando compila un'istruzione switch, il compilatore ispeziona ciascuna delle costanti case e crea una “tabella di salto” che utilizzerà per selezionare il percorso di esecuzione in base al valore dell'espressione. Pertanto, se occorre scegliere tra un ampio gruppo di valori, un'istruzione switch verrà eseguita molto più rapidamente rispetto alla logica equivalente realizzata con una sequenza di if-else. Il compilatore può farlo perché sa che tutte le costanti case sono dello stesso tipo e devono semplicemente essere confrontate per uguaglianza con l'espressione dello switch. Il compilatore non possiede tale conoscenza per un lungo elenco di espressioni if.