Pattern Matching con instanceof in Java
- Il pattern matching con
instanceof
consente di verificare il tipo di un oggetto e di assegnare una variabile pattern in un'unica espressione. - Questa funzionalità semplifica il codice, riducendo la necessità di cast espliciti e dichiarazioni separate.
- Il pattern matching può essere utilizzato in istruzioni
if
, ciclifor
ewhile
, ma non è consigliato in ciclido
a causa della limitazione della variabile pattern. - Il pattern matching è stato introdotto in JDK 16 e rappresenta un miglioramento significativo rispetto alla forma tradizionale di
instanceof
.
Pattern Matching con instanceof
Nella lezione precedente abbiamo studiato la forma tradizionale dell'operatore instanceof
.
Come spiegato lì, instanceof
restituisce true
se un oggetto è di un tipo specificato, o può essere convertito a quel tipo. A partire da JDK 16, è stata aggiunta a Java una seconda forma dell'operatore instanceof
che supporta la nuova funzionalità di pattern matching.
In termini generali, il pattern matching definisce un meccanismo che determina se un valore si adatta a una forma generale. Per quanto riguarda instanceof
, il pattern matching viene utilizzato per testare il tipo di un valore (che deve essere un tipo di riferimento) contro un tipo specificato. Questo tipo di pattern è chiamato type pattern. Se il pattern corrisponde, una pattern variable riceverà un riferimento all'oggetto che corrisponde al pattern.
La forma pattern matching di instanceof
è mostrata qui:
riferimento instanceof tipo pattern_var;
Se instanceof
ha successo, pattern_var
verrà creata e conterrà un riferimento all'oggetto che corrisponde al pattern. Se fallisce, pattern_var
non viene mai creata. Questa forma di instanceof
ha successo se l'oggetto riferito da riferimento
può essere convertito in tipo
e il tipo statico di riferimento
non è un sottotipo di tipo
.
Ad esempio, il seguente frammento crea un riferimento Number
chiamato mioOb
che si riferisce a un oggetto Integer
. Ricordiamo che Number
è una super-classe di tutti i wrapper di tipo primitivo numerico.
Quindi utilizza l'operatore instanceof
per confermare che l'oggetto riferito da mioOb
è un Integer
. Questo risulta in un oggetto chiamato iOgg
di tipo Integer
che viene istanziato e che contiene il valore corrispondente.
Number mioOb = Integer.valueOf(9);
// Usa la versione pattern matching di instanceof.
if(mioOb instanceof Integer iOgg) {
// iOgg è un oggetto valido di tipo Integer.
// all'interno dell'ambito dell'if.
System.out.println("iOgg si riferisce a un intero: " + iOgg);
}
// iOgg non esiste più qui
Come indicano i commenti, iOgg
è valido solo all'interno dello scope della clausola if
. Non è valido al di fuori dell'if
. Non sarebbe valido nemmeno all'interno di una clausola else
, se ne fosse stata inclusa una. È cruciale capire che la pattern variable iOgg
viene creata solo se il pattern matching ha successo.
Il vantaggio principale della forma pattern matching di instanceof
è che riduce la quantità di codice che era tipicamente necessaria con la forma tradizionale di instanceof
. Ad esempio, consideriamo questa versione funzionalmente equivalente dell'esempio precedente che utilizza l'approccio tradizionale:
// Usa un instanceof tradizionale.
if(mioOb instanceof Integer) {
// Usa un cast esplicito per ottenere iOgg.
Integer iOgg = (Integer) mioOb;
System.out.println("iOgg si riferisce a un intero: " + iOgg);
}
Con la forma tradizionale, sono richiesti una dichiarazione separata e un cast esplicito per creare la variabile iOgg
. La forma pattern matching di instanceof
semplifica il processo.
Variabili Pattern in un'Espressione AND Logica
Un instanceof
può essere utilizzato in un'espressione AND logica. Tuttavia, è necessario ricordare che la variabile pattern è in scope solo dopo che è stata creata. Ad esempio, il seguente if
ha successo solo quando mioOgg
si riferisce a un Integer
e il suo valore è non negativo.
Si presti particolare attenzione all'espressione nell'if
:
if((mioOgg instanceof Integer oggInt) && (oggInt >= 0)) {
// mioOgg è sia un Integer che non negativo.
// ...
}
La variabile pattern oggInt
viene creata solo se il lato sinistro dell'&&
(la parte che contiene l'operatore instanceof
) è true. Si noti che oggInt
è utilizzata anche dal lato destro. Questo è possibile perché viene utilizzata la forma a corto circuito dell'operatore AND logico, e il lato destro viene valutato solo se il sinistro ha successo. Quindi, se l'operando del lato destro viene valutato, oggInt
sarà in scope. Tuttavia, se avessimo provato a scrivere l'istruzione precedente utilizzando l'operatore &
così:
// ERRORE: Usa l'operatore & invece di &&
if((mioOgg instanceof Integer oggInt) & (oggInt >= 0)) {
// mioOgg è sia un Integer che non negativo.
// ...
}
si verificherebbe un errore di compilazione perché oggInt
non sarà in scope se il lato sinistro fallisce. Ricorda che l'operatore &
causa la valutazione di entrambi i lati dell'espressione, ma oggInt
è in scope solo se il lato sinistro è true. Questo errore viene catturato dal compilatore. Una situazione correlata si verifica con questo frammento:
int conteggio = 10;
// Corretto
if((conteggio < 100) && mioOgg instanceof Integer oggInt) {
// mioOgg è sia un Integer che non negativo, e conteggio è minore di 100.
oggInt = conteggio;
// ...
}
Questo frammento compila perché il blocco if
si eseguirà solo quando entrambi i lati dell'&&
sono true. Quindi, l'uso di oggInt
nel blocco if
è valido. Tuttavia, si verificherebbe un errore di compilazione se avessimo provato a usare &
invece di &&
, come mostrato qui:
// ERRORE: Usa l'operatore & invece di &&
if((conteggio < 100) & mioOgg instanceof Integer oggInt) {
/* ... */
}
In questo caso, il compilatore non può sapere se oggInt
sarà in scope nel blocco if
perché il lato destro dell'&
non sarà necessariamente valutato.
Un altro punto: Un'espressione logica non può introdurre la stessa variabile pattern più di una volta. Ad esempio, in un AND logico, è un errore se entrambi gli operandi creano la stessa variabile pattern.
Pattern Matching in Altre Istruzioni
Sebbene un uso frequente della forma di pattern matching di instanceof
sia in un'istruzione if
, non è affatto limitato a tale uso. Può anche essere impiegato nella parte condizionale delle istruzioni di ciclo. Come esempio, immaginiamo che stiamo elaborando una collezione di oggetti, forse contenuti in un array. Inoltre, all'inizio dell'array ci sono diverse stringhe, e vogliamo elaborare quelle stringhe, ma non gli altri oggetti rimanenti nella lista. La seguente sequenza porta a termine questo compito con un ciclo for
in cui la condizione usa instanceof
per confermare che un oggetto nell'array sia una String
e per ottenere quella stringa per l'elaborazione all'interno del ciclo. Quindi, il pattern matching viene utilizzato per controllare l'esecuzione di un ciclo for
e per ottenere il prossimo valore per l'elaborazione.
Object[] alcuniOggetti = {
new String("Alfa"),
new String("Beta"),
new String("Omega"),
Integer.valueOf(10)
};
int i;
// Questo ciclo itera finché un elemento non è una String,
// o la fine dell'array viene raggiunta.
for(i = 0; (alcuniOggetti[i] instanceof String str) && (i < alcuniOggetti.length); i++) {
System.out.println("Elaborando " + str);
// ...
}
System.out.println("Le prime " + i + " voci nella lista sono stringhe.");
L'output di questo frammento è mostrato qui:
Elaborando Alfa
Elaborando Beta
Elaborando Omega
Le prime 3 voci nella lista sono stringhe.
La forma di pattern matching di instanceof
può anche essere utile in un ciclo while
. Ad esempio, qui c'è il precedente ciclo for
, ricodificato come un while
:
i = 0;
while((alcuniOggetti[i] instanceof String str) && (i < alcuniOggetti.length)) {
System.out.println("Elaborando " + str);
i++;
}
Sebbene sia tecnicamente possibile utilizzare il pattern matching instanceof
nella parte condizionale di un ciclo do
, tale uso è severamente limitato perché la variabile pattern non può essere utilizzata nel corpo del ciclo perché non sarà nello scope finché l'operatore instanceof
non viene eseguito.