"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."
"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.
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?
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.
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 |
---|---|
|
Qui definiamo due classi: Cow e Whale . Whale eredita Cow .
La |
|
Questo codice mostra « Sono una mucca » sullo schermo. |
|
Questo codice mostra « Sono una balena » sullo schermo |
Dopo aver ereditato Cow
e sovrascritto printName
, la Whale
classe ha effettivamente i seguenti dati e metodi:
Codice | Descrizione |
---|---|
|
Non sappiamo nulla di alcun vecchio metodo. |
"Onestamente, è quello che mi aspettavo."
2) Ma non è tutto.
"Supponiamo che la Cow
classe abbia un printAll
metodo , che chiama gli altri due metodi. Allora il codice funzionerebbe così:"
Lo schermo mostrerà:
Sono bianco,
sono una balena
Codice | Descrizione |
---|---|
|
|
|
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 |
---|---|
|
Ecco come appare la classe Whale dopo aver applicato l'ereditarietà e aver sovrascritto il metodo. Non sappiamo nulla di alcun vecchio printName metodo. |
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 |
---|---|
|
Lo schermo mostrerà: sono bianco. |
|
Lo schermo mostrerà: sono bianco. |
|
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 |
---|---|
|
Lo schermo mostrerà: sono una balena. |
|
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 |
---|---|
|
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 |
---|---|
|
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 . |
|
È 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 |
---|---|
|
|
|
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».
GO TO FULL VERSION