Programmando in java, che sia per produrre codice nuovo o, ancora più probabilmente, modificando del codice già esistente, potreste aver trovato l’operatore java instanceof.
Ma cos’è instanceof in java? A cosa serve e come si usa?
Continuate a leggere e cercheremo di capirlo insieme!
Indice
- Cos’è l’operatore java instanceof?
- Esempio base
- Come funziona l’operatore instanceof in java?
- Uso di instanceof Object
- L’operatore instanceof con oggetto null
- Instanceof e generics
Cos’è l’operatore java instanceof?
Instanceof è un operatore binario che si usa per testare se un determinato oggetto è di un determinato tipo. Il risultato dell’espressione è un valore booleano true o false.
Prima di effettuare il cast di un oggetto sconosciuto (magari passato genericamente come Object), sarebbe prudente effettuare un controllo con instanceof. Questo ci eviterà di incorrere in una ClassCastException
in fase di esecuzione.
La sintassi di instanceof è abbastanza semplice. Si tratta di un operatore binario, che opera quindi su due argomenti, che in questo caso saranno un oggetto da testare e un tipo su cui testarlo.
(oggetto) instanceof (tipo)
Esempio base
Vediamo ora un esempio di base per capire come si usa l’operatore instanceof in java.
Creiamo una classe Animale:
public class Animale {
// implementazione
}
Aggiungiamo una classe Cane, che estende la classe Animale appena creata:
public class Cane extends Animale {
// implementazione
}
A questo punto possiamo creare una semplice classe di test e fare alcune prove con l’operatore instanceof:
@Test
public void caneInstanceOfAnimale() {
Cane cane = new Cane();
Assert.assertTrue(cane instanceof Animale);
}
Come funziona l’operatore instanceof in java?
L’operatore instanceof possiamo dire che risponde alla domanda “è un?”. Il suo funzionamento si basa sull’ereditarietà e sull’implementazione delle interfacce. L’oggetto x è quindi un Y se x è un’istanza di Y, se Y è una superclasse della classe di cui è istanza x, e se la classe di x implementa l’interfaccia Y.
Vediamo alcuni esempi pratici per capire meglio.
All’esempio precedente aggiungiamo l’interfaccia Quadrupede:
public interface Quadrupede {
// implementazione
}
E modifichiamo la classe Cane per implementare questa interfaccia:
public class Cane extends Animale implements Quadrupede{
// implementazione
}
A questo punto possiamo eseguire una serie di test per capire meglio le relazioni instanceof fra le varie classi.
Per prima cosa possiamo verificare che un oggetto cane, è instanceof Cane. Questo test da esito positivo in quanto l’oggetto è proprio istanza della classe che stiamo testando.
@Test
public void caneInstanceOfCane() {
Cane cane = new Cane();
Assert.assertTrue(cane instanceof Cane);
}
Possiamo verificare anche che questo vale se l’oggetto è istanza di una sottoclasse della classe testata (e quindi la classe testata è superclasse del tipo dell’istanza che stiamo testando):
@Test
public void caneInstanceOfAnimale() {
Cane cane = new Cane();
Assert.assertTrue(cane instanceof Animale);
}
Il terzo caso è quello in cui testiamo se la classe di cui è istanza il nostro oggetto implementa una determianta interfaccia. Anche in questo caso il test avrà esito positivo:
@Test
public void caneInstanceOfQuadrupede() {
Cane cane = new Cane();
Assert.assertTrue(cane instanceof Quadrupede);
}
Cosa succede se non c’è una relazione fra il tipo dell’oggetto e la classe testata?
Proviamo ora ad aggiungere un’ulteriore classe Gatto, che implementa Quadrupede ma che non ha alcuna relazione con la classe Cane:
public class Gatto implements Quadrupede {
// implementazione
}
In questo caso, se proviamo ad utilizzare l’operatore instanceof per testare se un cane è istanza di Gatto:
@Test
public void caneInstanceOfGatto() {
Cane cane = new Cane();
Assert.assertTrue(cane instanceof Gatto);
}
Riceveremo un errore di compilazione. Questo perché fondamentalmente non c’è alcuna relazione fra la classe Cane e la classe Gatto. Sarà quindi impossibile che un’istanza di qualunque tipo che possa essere assegnato alla variabile cane, sia istanza di Gatto. Il compilatore riconosce questo errore e ci impedisce di farlo.
java.lang.Error: Unresolved compilation problem:
Incompatible conditional operand types Cane and Gatto
Uso di instanceof Object
Come probabilmente sappiamo, in java ogni classe è implicitamente una sottoclasse di Object. Per questo motivo, se utilizziamo l’operatore instanceof con la classe Object, il risultato sarà sempre true, perché ogni oggetto istanza è implicitamente un object.
@Test
public void caneInstanceOfObject() {
Cane cane = new Cane();
Assert.assertTrue(cane instanceof Object);
}
L’operatore instanceof con oggetto null
La domanda potrebbe sorgere spontanea: se l’oggetto che testo è null, quale sarà il risultato dell’operatore instanceof? Come possiamo immaginare un oggetto null non è istanza di nessuna classe, per cui instanceof restituirà sempre false. L’operatore non ha bisogno di null check, possiamo quindi passare un oggetto null senza alcun problema, instanceof ci dirà sicuramente che non è istanza della classe testata e il codice andrà avanti.
@Test
public void nullInstanceofSomething() {
Cane cane = null;
Assert.assertFalse(cane instanceof Animale);
}
Instanceof e generics
Le operazioni di cast e test d’istanza, dipendono dalla possibilità di ispezionare le informazioni sul tipo di oggetto a runtime. Per questo motivo non possiamo utilizzare l’operatore instanceof su generics oggetto di type erasure.
Se as esempio proviamo a compilare del codice contenente un instanceof come il seguente:
public static <T> void sort(List<T> collection) {
if (collection instanceof List<String>) {
// ordina in un modo
}
// ordina in un altro modo
}
otterremo un errore di compilazione in quanto non possiamo utilizzare List<String> come oggetto nell’operazione instanceof
error: illegal generic type for instanceof
if (collection instanceof List<String>) {
Possiamo quindi dire che è possibile utilizzare l’operatore instanceof in java solamente con tipi reificati. Un tipo è reificato se le informazioni sul tipo sono presenti a runtime (non sono quindi oggetto di type erasure).
Alcuni esempi di tipi reificati sono:
- I tipi primitivi come int, long
- Le classi e interfacce non generiche, come ad esempio String
- Tipi generici in cui tutti i tipi sono costituiti da “unbounded wildcards” come Set<?>
- Tipi specifici come List o HashMap
- Array reificati come String[], List[]
Possiamo quindi fare due esempi:
public static <T> boolean isOfType(Object input) {
return input instanceof T; // non compila!
}
In questo primo esempio il codice non compilerà, perché abbiamo utlizzato il parametro generic non reificato T.
if (collection instanceof List<?>) {
// codice
}
In questo secondo caso invece il codice funziona in quanto List<?> è ammesso.