1. Tipografia

Le variabili che memorizzano tipi di riferimento (classi) possono anche essere convertite in tipi diversi. Ma questo funziona solo all'interno di una singola gerarchia di tipo. Diamo un'occhiata a un semplice esempio. Supponiamo di avere la seguente gerarchia di classi, in cui le classi sottostanti ereditano le classi sopra.

Tipografia

Anche il typecasting dei tipi di riferimento e di quelli primitivi è classificato come ampliamento e restringimento.

Vediamo che la classe Cat eredita la classe Pet e la classe Pet, a sua volta, eredita la classe Animal.

Se scriviamo codice come questo:

Animal kitten = new Cat();

Questa è una conversione di tipo allargamento . È anche chiamato cast implicito. Abbiamo ampliato il riferimento cat in modo che ora si riferisca a un oggetto Cat . Con una conversione di tipo come questa, non saremo in grado di utilizzare il riferimento gattino per chiamare metodi presenti nella classe Cat ma assenti nella classe Animal .

Una conversione di restringimento (o cast esplicito) avviene nella direzione opposta:

Cat cat = (Cat) kitten;

Abbiamo indicato esplicitamente che vogliamo eseguire il cast del riferimento memorizzato nella variabile gattino (il cui tipo è Animal ) al tipo Cat .



2. Verifica del tipo di un oggetto

Ma devi stare molto attento qui. Se lo fai:

Animal beast = new Cat();
Wolf grayWolf = (Wolf) beast;

Il compilatore consentirà questo codice, ma ci sarà un errore durante l'esecuzione del programma! La JVM genererà un'eccezione:

Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to a Wolf

I riferimenti a un oggetto Cat possono essere memorizzati solo in variabili il cui tipo è un antenato della classe Cat: Pet, Animal o Object.

Perché?

Il punto rilevante qui è che un riferimento a un oggetto viene utilizzato per fare riferimento ai metodi e alle variabili di quell'oggetto . E non ci saranno problemi se usiamo una variabile Animal per memorizzare un riferimento a un oggetto Cat: il tipo Cat ha sempre variabili e metodi del tipo Animal — li ​​ha ereditati!

Ma se la JVM ci permettesse di memorizzare un riferimento a un oggetto Cat in una variabile Wolf, allora potremmo avere una situazione in cui potremmo provare a usare la variabile grayWolf per chiamare un metodo che non esiste nell'oggetto Cat memorizzato in quella variabile . Ecco perché questa disposizione non è consentita.

Java ha un operatore speciale instanceofche ti consente di verificare se un oggetto è di un certo tipo e quindi può essere memorizzato in una variabile di un certo tipo. Sembra abbastanza semplice:

variable instanceof Type

Esempio:

Animal beast = new Cat();
if (beast instanceof Wolf)
{
   Wolf grayWolf = (Wolf) beast;
}

Questo codice non causerà errori, nemmeno in fase di esecuzione.

Ecco alcuni altri esempi che illustrano la situazione:

Conversione del tipo di ampliamento Descrizione
Cow cow = new Whale();

Questa è una classica conversione di ampliamento: non è richiesto alcun operatore di conversione del tipo. Ora solo i metodi definiti nella Cowclasse possono essere chiamati sull'oggetto Whale.

Sulla cowvariabile , il compilatore ti consentirà solo di chiamare i metodi che Cowha il suo tipo (la classe).

Conversione del tipo di restringimento
Cow cow = new Whale();
if (cow instanceof Whale)
{
   Whale whale = (Whale) cow;
}
Conversione di restringimento classica: è necessario aggiungere un controllo del tipo e un operatore di cast.
La Cow cowvariabile memorizza un riferimento a un Whaleoggetto.
Verifichiamo che questo sia il caso e quindi eseguiamo una conversione di tipo (restringente). O come viene anche chiamato:
un cast tipo
.

Cow cow = new Cow();
Whale whale = (Whale) cow; // Exception
È possibile restringere un tipo di riferimento senza controllare il tipo dell'oggetto.
Se la cowvariabile fa riferimento a un oggetto che non è a Whale, InvalidClassCastExceptionverrà generato un oggetto.


3. Chiamare il metodo originale: la superparola chiave

Quando sovrascriviamo il metodo di una classe genitore, a volte invece di sostituirlo con il nostro, vogliamo solo integrarlo leggermente.

Sarebbe bello se potessimo inserire il metodo della classe genitore nel nostro metodo e quindi eseguire parte del nostro codice. O forse prima esegui il nostro codice e poi chiama il metodo della classe genitore.

E Java ci consente proprio questo. Per chiamare un metodo della classe genitore, procedere come segue:

super.method(arguments);

Esempi:

class PeaceTime
{
   public double getPi()
   {
      return 3.14;
   }
}

class WarTime extends PeaceTime
{
   public double getPi()
   {
      return super.getPi()*2;  // 3.14*2
   }
}

In tempo di guerra, il valore di Pipuò essere maggiore di 6! Certo, stiamo scherzando, ma questo esempio dimostra come tutto questo può funzionare.

Ecco un altro paio di esempi per chiarire un po' le cose:

Codice Descrizione
class Cow
{
   public void printAll()
   {
      printColor();
      printName();
   }

   public void printColor()
   {
      System.out.println("I'm a white whale");
   }

   public void printName()
   {
      System.out.println("I'm a cow");
   }
}

class Whale extends Cow
{
   public void printName()
   {
      System.out.print("This is incorrect: ");
      super.printName();
      System.out.println("I'm a whale");
   }
}
Cowe Whaleclassi
public static void main(String[] args)
{
   Whale whale = new Whale();
   whale.printAll();
}
L'output dello schermo sarà:
I'm a white whale
This is incorrect: I'm a cow
I'm a whale

Questa è roba difficile. Onestamente, è una delle cose più difficili in OOP . Detto questo, devi conoscerlo e capirlo.