W3docs

Pattern Matching in Java

Usa il pattern matching in Java per instanceof e switch — pattern di tipo, record pattern e decostruzione.

Per anni, il codice Java che lavorava con valori di tipo sconosciuto seguiva un rituale noioso: verificare il tipo con instanceof, poi eseguire il cast a quel tipo, poi usarlo. Il pattern matching riduce questo rituale a una singola espressione. Un pattern descrive la forma dei dati; se un valore corrisponde, Java associa le sue parti a variabili utilizzabili immediatamente — senza cast manuali.

Il pattern matching è arrivato per fasi: prima i pattern per instanceof, poi i pattern in switch, poi i record pattern che decostruiscono i record nei loro componenti. Insieme permettono di scrivere codice dichiarativo e type-safe che rispecchia i dati su cui opera.

Questo capitolo tratta il pattern per instanceof, i pattern di tipo in switch, i pattern con guardia e la gestione di null, e i record pattern — poi li collega in un programma eseguibile. Si basa su tre funzionalità che potresti voler rivedere prima: l'operatore instanceof, i record e le espressioni switch.

Pattern Matching per instanceof

Il classico pattern test-e-cast richiedeva tre riferimenti allo stesso tipo. Il pattern per instanceof associa una variabile nello stesso momento del test, e l'associazione è in scope ovunque il test risulti vero.

Object value = "hello";

// Old way: test, then cast
if (value instanceof String) {
    String s = (String) value;
    System.out.println(s.length());
}

// Pattern way: test and bind together
if (value instanceof String s) {
    System.out.println(s.length());
}

Poiché la variabile di associazione partecipa all'espressione booleana, puoi continuare a restringere nello stesso if. Il compilatore verifica che s sia sicuro da usare:

if (value instanceof String s && s.length() > 3) {
    System.out.println(s.toUpperCase());
}

Pattern in switch

Uno switch può eseguire la corrispondenza su pattern di tipo, effettuando il dispatch in base al tipo runtime del selettore. Ogni case associa il valore corrisposto, così il corpo lavora direttamente con una variabile tipizzata. Questo trasforma lunghe catene if/else instanceof in una tabella compatta e leggibile.

static String format(Object value) {
    return switch (value) {
        case Integer i -> "int: " + i;
        case Long l    -> "long: " + l;
        case String s  -> "string: " + s;
        default        -> "other: " + value;
    };
}

Uno switch con pattern di tipo deve essere esaustivo — deve coprire ogni possibile input. Per selettori di tipo Object arbitrario ciò significa un ramo default; per le gerarchie sealed il compilatore conosce l'insieme completo dei sottotipi e può verificare l'esaustività senza un default.

Pattern con Guardia e null

Una clausola when aggiunge una condizione booleana a un case, permettendo a due valori dello stesso tipo di prendere rami diversi. Questo si chiama pattern con guardia, e l'ordine conta: i case con guardia più specifici vengono prima del fallback senza guardia.

static String size(String s) {
    return switch (s) {
        case String t when t.isEmpty() -> "empty";
        case String t when t.length() < 5 -> "short";
        case String t -> "long (" + t.length() + ")";
    };
}

Tradizionalmente uno switch lanciava NullPointerException su un selettore null. Uno switch con pattern può gestire null esplicitamente con un case null, mantenendo il controllo null all'interno dello stesso costrutto invece di una guardia separata prima di esso.

FunzionalitàSintassiScopo
Pattern di tipocase String sCorrispondenza per tipo e associazione
Pattern con guardiacase String s when s.isEmpty()Aggiunge una condizione a un case
Etichetta nullcase nullCorrisponde a un selettore null
Record patterncase Point(int x, int y)Decostruisce un record

Record Pattern

Un record pattern corrisponde a un record e associa i suoi componenti in un'unica operazione, evitando le chiamate agli accessor. Poiché i record espongono i loro componenti, il compilatore conosce la forma esatta e permette di nominare ogni parte inline. I record pattern si annidano, quindi puoi destrutturare un record di record.

record Point(int x, int y) {}
record Line(Point start, Point end) {}

static String render(Object o) {
    return switch (o) {
        case Point(int x, int y) -> "point " + x + "," + y;
        // Nested: pull both endpoints' coordinates out at once
        case Line(Point(int x1, int y1), Point(int x2, int y2)) ->
            "line " + x1 + "," + y1 + " -> " + x2 + "," + y2;
        default -> "unknown";
    };
}

Il pattern matching eccelle con i tipi sealed: quando un'interfaccia elenca le sue implementazioni consentite, uno switch su di esse è esaustivo senza un default, e l'aggiunta di un nuovo sottotipo trasforma il case mancante in un errore di compilazione invece di un bug silenzioso.

Un Esempio Completo ed Eseguibile

Il programma seguente collega tutti i pezzi insieme. Usa un pattern instanceof con una guardia, una gerarchia Shape sealed di record, record pattern che decostruiscono ogni forma in uno switch, un pattern con guardia che individua un quadrato, e un case null — tutto senza un singolo cast esplicito.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • describe(42) stampa positive int 42 perché la guardia instanceof Integer i && i > 0 verifica il tipo e il valore insieme prima di associare i.
  • describe(-5) ricade su unknown — lo stesso pattern Integer corrisponde al tipo ma la guardia i > 0 fallisce, mostrando come una guardia raffina un pattern di tipo.
  • Lo switch di area non necessita di default: Shape è sealed, quindi elencare Circle, Rectangle e Triangle è esaustivo e il compilatore è soddisfatto.
  • Il rettangolo 5.0 x 5.0 viene stampato come square side=5.0 perché il suo case con guardia when w == h è posizionato prima del case generale Rectangle r e ha la precedenza.
  • L'ultima riga stampa no shape: il ramo case null gestisce un selettore null all'interno dello switch invece di lanciare NullPointerException.

Esercitazione

Pratica
In uno switch con pattern, cosa fa l'aggiunta di una clausola 'when' a un case?
In uno switch con pattern, cosa fa l'aggiunta di una clausola 'when' a un case?
Was this page helpful?