Ereditarietà
Nelle lezioni precedenti, hai visto l’ereditarietà menzionata più volte. Nel linguaggio Java, le classi possono essere derivate da altre classi, ereditando così campi e metodi da quelle classi.
Ad eccezione di
Object
, che non ha superclasse, ogni classe ne ha una e solo una superclasse diretta (eredità singola). In assenza di qualsiasi altra superclasse esplicita, ogni classe è implicitamente una sottoclasse di Object
. Le classi possono essere derivate da classi derivate da classi derivate da classi e così via, e in definitiva derivato dalla classe più in alto,
Object
. Si dice che una tale classe discenda da tutte le classi nella catena di ereditarietà che risale a Object
. L’idea dell’ereditarietà è semplice ma potente: quando vuoi creare una nuova classe ed esiste già una classe che include parte del codice che desideri, puoi derivare la tua nuova classe dalla classe esistente . In questo modo, puoi riutilizzare i campi e i metodi della classe esistente senza doverli scrivere (ed eseguire il debug!).
Una sottoclasse eredita tutti i membri (campi, metodi e classi nidificate) da la sua superclasse. I costruttori non sono membri, quindi non vengono ereditati dalle sottoclassi, ma il costruttore della superclasse può essere richiamato dalla sottoclasse.
Gerarchia delle classi della piattaforma Java
Il Object
, definita nel pacchetto java.lang
, definisce e implementa il comportamento comune a tutte le classi, comprese quelle che scrivi. Nella piattaforma Java, molte classi derivano direttamente da Object
, altre derivano da alcune di queste classi e così via, formando una gerarchia di classi.
Tutte le classi nella piattaforma Java sono discendenti di oggetti
In cima alla gerarchia, Object
è la più generale di tutte le classi. Le classi nella parte inferiore della gerarchia forniscono un comportamento più specializzato.
Un esempio di ereditarietà
Di seguito è riportato il codice di esempio per una possibile implementazione di un Bicycle
che è stata presentata nella lezione Classi e oggetti:
Una dichiarazione di classe per una classe MountainBike
che è una sottoclasse di Bicycle
potrebbe avere questo aspetto:
public class MountainBike extends Bicycle { // the MountainBike subclass adds one field public int seatHeight; // the MountainBike subclass has one constructor public MountainBike(int startHeight, int startCadence, int startSpeed, int startGear) { super(startCadence, startSpeed, startGear); seatHeight = startHeight; } // the MountainBike subclass adds one method public void setHeight(int newValue) { seatHeight = newValue; } }
MountainBike
eredita tutti i campi e i metodi di Bicycle
e aggiunge il campo seatHeight
e un metodo per impostarlo. Ad eccezione del costruttore, è come se avessi scritto una nuova classe MountainBike
interamente da zero, con quattro campi e cinque metodi. Tuttavia, non dovevi fare tutto il lavoro. Ciò sarebbe particolarmente utile se i metodi nella classe Bicycle
fossero complessi e avessero richiesto molto tempo per il debug.
Cosa puoi fare in una sottoclasse
Una sottoclasse eredita tutti i membri pubblici e protetti del suo genitore, indipendentemente dal pacchetto in cui si trova la sottoclasse. Se la sottoclasse è nello stesso pacchetto della sua genitore, eredita anche i membri privati del pacchetto del genitore. Puoi utilizzare i membri ereditati così come sono, sostituirli, nasconderli o completarli con nuovi membri:
- I campi ereditati possono essere utilizzato direttamente, proprio come qualsiasi altro campo.
- Puoi dichiarare un campo nella sottoclasse con lo stesso nome di quello nella superclasse, nascondendolo (sconsigliato).
- Puoi dichiarare nuovi campi nella sottoclasse che non sono nella superclasse.
- I metodi ereditati possono essere utilizzati direttamente così come sono.
- Puoi scrivere un nuovo metodo di istanza nel sottoclasse quello ha la stessa firma di quella nella superclasse, quindi sovrascrivendola.
- Puoi scrivere un nuovo metodo statico nella sottoclasse che abbia la stessa firma di quello nella superclasse, nascondendolo così.
- Puoi dichiarare nuovi metodi nella sottoclasse che non sono nella superclasse.
- Puoi scrivere un costruttore di sottoclassi che invoca il costruttore della superclasse, sia implicitamente che usando la parola chiave
super
.
Le seguenti sezioni di questa lezione si espanderanno su questi argomenti.
Membri privati in una Superclasse
Una sottoclasse non eredita i private
membri della sua classe genitore. Tuttavia, se la superclasse ha metodi pubblici o protetti per accedere ai suoi campi privati, questi possono essere utilizzati anche dalla sottoclasse.
Una classe annidata ha accesso a tutti i membri privati della sua classe che la racchiude, sia campi che metodi. Pertanto, una classe nidificata pubblica o protetta ereditata da una sottoclasse ha accesso indiretto a tutti i membri privati della superclasse.
Casting di oggetti
Abbiamo visto che un oggetto è del tipo di dati della classe da cui è stata creata un’istanza. Ad esempio, se scriviamo
public MountainBike myBike = new MountainBike();
allora myBike
è di tipo MountainBike
.
MountainBike
discende da Bicycle
e Object
. Pertanto, un MountainBike
è un Bicycle
ed è anche un Object
e può essere utilizzato ovunque siano richiesti oggetti Bicycle
o Object
.
Non è necessariamente vero il contrario: a Bicycle
può essere un MountainBike
, ma non lo è “necessariamente. Allo stesso modo, un Object
può essere un Bicycle
o MountainBike
, ma non lo è necessariamente.
Il casting mostra l’uso di un oggetto di uno tipo al posto di un altro tipo, tra gli oggetti consentiti dall’ereditarietà e dalle implementazioni. Ad esempio, se scriviamo
Object obj = new MountainBike();
allora obj
è sia un Object
e un MountainBike
(fino a quando obj
non viene assegnato un altro oggetto che non è un MountainBike
). Questo è chiamato casting implicito.
Se, d’altra parte, scriviamo
MountainBike myBike = obj;
lo faremmo riceve un errore in fase di compilazione perché obj
non è noto al compilatore come MountainBike
. Tuttavia, possiamo dire al compilatore che promettiamo di assegnare un MountainBike
a obj
tramite casting esplicito:
MountainBike myBike = (MountainBike)obj;
Questo cast inserisce un controllo di runtime che a obj
sia assegnato un MountainBike
in modo che il compilatore possa tranquillamente presumere che obj
sia un MountainBike
. Se obj
non è un MountainBike
in fase di esecuzione, verrà generata un’eccezione.
instanceof
. Questo può salvarti da un errore di runtime dovuto a un cast improprio. Ad esempio:
if (obj instanceof MountainBike) { MountainBike myBike = (MountainBike)obj;}
Qui l’operatore instanceof
verifica che obj
si riferisce a un MountainBike
in modo che possiamo eseguire il cast con la consapevolezza che non verrà generata alcuna eccezione di runtime.