"Amigo, ti piacciono le balene?"

"Balene? No, non ne ho mai sentito parlare."

"È come una mucca, solo che è più grande e nuota. Per inciso, le balene derivano dalle mucche. Uh, o almeno condividono un antenato comune. Non importa."

Polimorfismo e override - 1

"Ascolta. Voglio parlarti di un altro strumento molto potente di OOP: il polimorfismo . Ha quattro caratteristiche."

1) Override del metodo.

Immagina di aver scritto una classe "Mucca" per un gioco. Ha un sacco di variabili membro e metodi. Gli oggetti di questa classe possono fare varie cose: camminare, mangiare, dormire. Anche le mucche suonano un campanello quando camminano. Diciamo che hai implementato tutto nella classe fin nei minimi dettagli.

Polimorfismo e override - 2

Poi improvvisamente il cliente dice che vuole rilasciare un nuovo livello del gioco, dove tutte le azioni si svolgono in mare, e il personaggio principale è una balena.

Hai iniziato a progettare la classe Balena e ti sei reso conto che è solo leggermente diversa dalla classe Mucca. Entrambe le classi utilizzano una logica molto simile e decidi di utilizzare l'ereditarietà.

La classe Cow è ideale per essere la classe genitore: ha già tutte le variabili ei metodi necessari. Tutto quello che devi fare è aggiungere la capacità della balena di nuotare. Ma c'è un problema: la tua balena ha zampe, corna e un campanello. Dopotutto, la classe Cow implementa questa funzionalità. Cosa sai fare?

Polimorfismo e override - 3

L'override del metodo viene in soccorso. Se ereditiamo un metodo che non fa esattamente ciò di cui abbiamo bisogno nella nostra nuova classe, possiamo sostituire il metodo con un altro.

Polimorfismo e override - 4

Come si fa? Nella nostra classe discendente, dichiariamo il metodo che vogliamo modificare (con la stessa firma del metodo della classe genitore) . Poi scriviamo nuovo codice per il metodo. Questo è tutto. È come se il vecchio metodo della classe genitore non esistesse.

Ecco come funziona:

Codice Descrizione
class Cow
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
Qui definiamo due classi:  Cow e  WhaleWhaleeredita  Cow.

La  Whale classe esegue l'override del  printName();metodo.

public static void main(String[] args)
{
Cow cow = new Cow();
cow.printName();
}
Questo codice mostra « Sono una mucca » sullo schermo.
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
Questo codice mostra « Sono una balena » sullo schermo

Dopo aver ereditato Cowe sovrascritto printName, la Whaleclasse ha effettivamente i seguenti dati e metodi:

Codice Descrizione
class Whale
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
Non sappiamo nulla di alcun vecchio metodo.

"Onestamente, è quello che mi aspettavo."

2) Ma non è tutto.

"Supponiamo che la  Cow classe abbia un  printAllmetodo , che chiama gli altri due metodi. Allora il codice funzionerebbe così:"

Lo schermo mostrerà:
Sono bianco,
sono una balena

Codice Descrizione
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
Lo schermo mostrerà:
Sono bianco,
sono una balena

Si noti che quando il metodo printAll() della classe Cow viene chiamato su un oggetto Whale, verrà utilizzato il metodo printName() di Whale, non quello di Cow.

L'importante non è la classe in cui è scritto il metodo, ma piuttosto il tipo (classe) dell'oggetto su cui viene chiamato il metodo.

"Vedo."

"Puoi solo ereditare e sovrascrivere metodi non statici. I metodi statici non vengono ereditati e quindi non possono essere sovrascritti."

Ecco come appare la classe Whale dopo aver applicato l'ereditarietà e sovrascritto i metodi:

Codice Descrizione
class Whale
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
Ecco come appare la classe Whale dopo aver applicato l'ereditarietà e aver sovrascritto il metodo. Non sappiamo nulla di alcun vecchio printNamemetodo.

3) Colata di tipo.

Ecco un punto ancora più interessante. Poiché una classe eredita tutti i metodi ei dati della sua classe genitore, un oggetto di questa classe può essere referenziato dalle variabili della classe genitore (e dal genitore del genitore, ecc., fino alla classe Object). Considera questo esempio:

Codice Descrizione
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printColor();
}
Lo schermo mostrerà:
sono bianco.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printColor();
}
Lo schermo mostrerà:
sono bianco.
public static void main(String[] args)
{
Object o = new Whale();
System.out.println(o.toString());
}
Lo schermo mostrerà:
Whale@da435a.
Il metodo toString() viene ereditato dalla classe Object.

"Roba buona. Ma perché ne avresti bisogno?"

"È una caratteristica preziosa. Capirai in seguito che è molto, molto preziosa."

4) Rilegatura tardiva (spedizione dinamica).

Ecco come appare:

Codice Descrizione
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
Lo schermo mostrerà:
sono una balena.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printName();
}
Lo schermo mostrerà:
sono una balena.

Si noti che non è il tipo di variabile che determina quale specifico metodo printName chiamiamo (quello della classe Cow o Whale), ma piuttosto il tipo di oggetto referenziato dalla variabile.

La variabile Cow memorizza un riferimento a un oggetto Whale e verrà chiamato il metodo printName definito nella classe Whale .

"Beh, non l'hanno aggiunto per motivi di chiarezza."

"Sì, non è così ovvio. Ricorda questa regola importante:"

L'insieme di metodi che puoi chiamare su una variabile è determinato dal tipo della variabile. Ma quale metodo/implementazione specifico viene chiamato è determinato dal tipo/classe dell'oggetto a cui fa riferimento la variabile.

"Ci proverò."

"Ti imbatterai costantemente in questo, quindi lo capirai rapidamente e non lo dimenticherai mai."

5) Tipo fusione.

Il casting funziona in modo diverso per i tipi di riferimento, ad esempio le classi, rispetto ai tipi primitivi. Tuttavia, le conversioni di ampliamento e restringimento si applicano anche ai tipi di riferimento. Considera questo esempio:

Conversione allargata Descrizione
Cow cow = new Whale();

Una classica conversione allargata. Ora puoi chiamare solo i metodi definiti nella classe Cow sull'oggetto Whale.

Il compilatore ti consentirà di utilizzare la variabile cow solo per chiamare quei metodi definiti dal tipo Cow.

Conversione restringente Descrizione
Cow cow = new Whale();
if (cow instanceof Whale)
{
Whale whale = (Whale) cow;
}
Una classica conversione di restringimento con un controllo del tipo. La variabile mucca di tipo Mucca memorizza un riferimento a un oggetto Balena.
Verifichiamo che questo sia il caso , quindi eseguiamo la conversione del tipo (allargamento). Questo è anche chiamato casting di tipo .
Cow cow = new Cow();
Whale whale = (Whale) cow; //exception
È inoltre possibile eseguire una conversione di restringimento di un tipo di riferimento senza controllare il tipo dell'oggetto.
In questo caso, se la variabile cow punta a qualcosa di diverso da un oggetto Whale, verrà lanciata un'eccezione (InvalidClassCastException).

6) E ora per qualcosa di gustoso. Chiamare il metodo originale.

A volte, quando si esegue l'override di un metodo ereditato, non si desidera sostituirlo completamente. A volte vuoi solo aggiungere un po 'ad esso.

In questo caso, vuoi davvero che il codice del nuovo metodo chiami lo stesso metodo, ma sulla classe base. E Java ti consente di farlo. Ecco come si fa:  super.method().

Ecco alcuni esempi:

Codice Descrizione
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.print("This is false: ");
super.printName();

System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
Lo schermo mostrerà:
Sono bianco
Questo è falso: sono una mucca
sono una balena

"Hmm. Beh, questa è stata una lezione. Le mie orecchie da robot si sono quasi sciolte."

"Sì, questa non è roba semplice. È uno dei materiali più difficili che incontrerai. Il professore ha promesso di fornire collegamenti a materiali di altri autori, in modo che se ancora non capisci qualcosa, puoi compilare il lacune».