"Ciao, Amigo! L'argomento della lezione di oggi è l'allargamento e il restringimento delle conversioni di tipo. Hai imparato a ampliare e restringere i tipi primitivi molto tempo fa. Al livello 10. Oggi parleremo di come funziona per i tipi di riferimento, ad es. istanze di classi”.

In effetti, è tutto abbastanza semplice. Immagina la catena di ereditarietà di una classe: la classe, il suo genitore, il genitore del genitore, ecc., fino alla classe Object. Poiché una classe contiene tutti i metodi membro della classe che eredita , un'istanza della classe può essere salvata in una variabile il cui tipo è quello di uno dei suoi genitori.

Ecco un esempio:

Codice Descrizione
class Animal
{
public void doAnimalActions();
}class Cat extends Animal
{
public void doCatActions();
}class Tiger extends Cat
{
public void doTigerActions();
}
Qui abbiamo tre dichiarazioni di classe: Animale, Gatto e Tigre. Il gatto eredita l'animale. E Tiger eredita Cat.
public static void main(String[] args)
{
Tiger tiger = new Tiger();
Cat cat = new Tiger();
Animal animal = new Tiger();
Object obj = new Tiger();
}
Un oggetto Tiger può sempre essere assegnato a una variabile il cui tipo è quello di uno dei suoi predecessori. Per la classe Tigre, questi sono Gatto, Animale e Oggetto.

Ora diamo un'occhiata all'allargamento e al restringimento delle conversioni.

Se un'operazione di assegnazione ci fa risalire la catena di ereditarietà (verso la classe Object), allora abbiamo a che fare con una conversione di ampliamento (nota anche come upcasting). Se ci spostiamo lungo la catena verso il tipo di oggetto, si tratta di una conversione restringente (nota anche come downcasting).

Lo spostamento verso l'alto della catena di ereditarietà si chiama ampliamento, perché porta a un tipo più generale. Tuttavia, così facendo perdiamo la possibilità di invocare i metodi aggiunti alla classe attraverso l'ereditarietà.

Codice Descrizione
public static void main(String[] args)
{
Object obj = new Tiger();
Animal animal = (Animal) obj;
Cat cat = (Cat) obj;
Tiger tiger = (Tiger) animal;
Tiger tiger2 = (Tiger) cat;
}
Quando si restringe il tipo, è necessario utilizzare un operatore di conversione del tipo, ovvero eseguiamo una conversione esplicita.

Ciò fa sì che la macchina Java controlli se l'oggetto eredita davvero il tipo in cui vogliamo convertirlo.

Questa piccola innovazione ha prodotto una molteplice riduzione del numero di errori di casting del tipo e ha aumentato significativamente la stabilità dei programmi Java.

Codice Descrizione
public static void main(String[] args)
{
Object obj = new Tiger();
if (obj instanceof Cat)
{
Cat cat = (Cat) obj;
cat.doCatActions();
}}
Meglio ancora, usa  un'istanza di check
public static void main(String[] args)
{
Animal animal = new Tiger();
doAllAction(animal);

Animal animal2 = new Cat();
doAllAction(animal2);

Animal animal3 = new Animal();
doAllAction(animal3);
}

public static void doAllAction(Animal animal)
{
if (animal instanceof Tiger)
{
Tiger tiger = (Tiger) animal;
tiger.doTigerActions();
}

if (animal instanceof Cat)
{
Cat cat = (Cat) animal;
cat.doCatActions();
}

animal.doAnimalActions();
}
Ed ecco perché. Dai un'occhiata all'esempio a sinistra.

Noi (il nostro codice) non sempre sappiamo con che tipo di oggetto stiamo lavorando. Potrebbe essere un oggetto dello stesso tipo della variabile (Animale) o qualsiasi tipo discendente (Gatto, Tigre).

Considera il metodo doAllAction. Funziona correttamente, indipendentemente dal tipo di oggetto passato.

In altre parole, funziona correttamente per tutti e tre i tipi: Animale, Gatto e Tigre.

public static void main(String[] args)
{
Cat cat = new Tiger();
Animal animal = cat;
Object obj = cat;
}
Qui abbiamo tre operazioni di assegnazione. Sono tutti esempi di conversioni in espansione.

L'operatore type cast non è necessario qui, perché non è necessario alcun controllo. Un riferimento a un oggetto può sempre essere memorizzato in una variabile il cui tipo è uno dei suoi predecessori.

"Oh, il penultimo esempio ha chiarito tutto: perché è necessario il controllo e perché è necessario il casting del tipo."

"Lo spero. Voglio attirare la tua attenzione su questo fatto:"

Niente di tutto ciò fa cambiare un oggetto in alcun modo! L'unica cosa che cambia è il numero di metodi disponibili per essere chiamati su una particolare variabile di riferimento.

Ad esempio, una variabile Cat consente di chiamare i metodi doAnimalActions e doCatActions. Non sa nulla del metodo doTigerActions, anche se punta a un oggetto Tiger.

"Sì, ho capito. È stato più facile di quanto pensassi."