![]() ![]() |
|
Nel contesto del paradigma di programmazione orientato
ad oggetti, l'ereditarietà è un meccanismo fondamentale
sia per il riutilizzo del codice che per supportare lo sviluppo incrementale
di programmi.
Una classe definisce struttura e funzionalità di una collezione di oggetti. In molti casi occorre definire una classe i cui oggetti hanno una struttura più ricca di quella di una classe già definita, oppure che realizzano delle funzionalità aggiuntive. In questi casi non c'è bisogno di ridefinire variabili e metodi d'istanza già definiti, ma si può definire la nuova classe come sottoclasse della precedente. |
![]() ![]() |
|
|
![]() ![]() |
|
Vogliamo definire una classe Tempo2
che rappresenti il tempo con maggior precisione, cioè con i centesimi
di secondo. L'ereditarietà ci consente di definire questa
classe senza ripetere la descrizione di tutte le variabili e i metodi di
Tempo,
ma in modo incrementale:
La parola chiave extends significa che
|
![]() ![]() |
|
![]() Un'istanza di Tempo2 avrà quattro variabili di istanza:
|
![]() ![]() |
|
La definizione di sottoclassi può essere
iterata a piacere: si possono definire delle gerarchie di classi
arbitrariamente complesse.
Ogni classe è una classe derivata, in modo diretto o indiretto, dalla classe Object. Questo spiega perché su ogni oggetto Java possono essere invocati metodi come equals e toString, definiti nella classe Object.
|
![]() ![]() |
|
E' possibile utilizzare un'istanza di una sottoclasse
(Tempo2) dovunque sia
richiesto un oggetto della superclasse (Tempo),
come in un assegnamento o nel passaggio di parametri.
|
![]() ![]() |
|
Attenzione: Non è possibile invocare
un metodo di una sottoclasse su di un identificatore di una superclasse.
In certe situazioni è utile/necessario invocare
su di un identificatore un metodo di una sottoclasse [vedremo un esempio
concreto più avanti con la classe VettoreOrdinato]:
si può usare l'operazione di cast.
Esempio: TestTempo.java
Avevamo già visto il cast per trasformare valori numerici:
Il compilatore consente di effettuare un cast solo verso classi derivate o genitrici (ma in quest'ultimo caso il cast è superfluo). Quando si valuta ((Tempo2) arrivo) se arrivo non si riferisce ad un'istanza di Tempo2, verrà generato un errore. Si può controllare la classe di appartenenza di
un oggetto prima del cast, come in
La condizione (obj instanceof Classe) restituisce true se e solo se obj è una istanza della classe Classe. |
![]() ![]() |
|
Una sottoclasse può anche modificare
dei metodi della superclasse, ridefinendoli.
Ad esempio, se invochiamo visualizza su
un'istanza di Tempo2, verranno stampati solo i valori delle
prime tre variabili d'istanza. Possiamo sovrascrivere (override)
visualizza
aggiungendo a Tempo2:
Il comando t.visualizza(true) invocherà il metodo visualizza della classe Tempo se t è un'istanza di Tempo, ma invocherà il nuovo metodo che stampa anche i centesimi se t è un'istanza di Tempo2. |
![]() ![]() |
|
Il meccanismo che determina quale metodo deve
essere invocato in base alla classe di appartenenza dell'oggetto si chiama
binding.
Nell'ultimo comando, il compilatore non può sapere
se a
tmp sarà associato un'istanza di Tempo
o di Tempo2, perché questo dipenderà dal
valore fornito dall'utente.
|
![]() ![]() |
|
Il meccanismo di overriding è concettualmente
molto diverso da quello di overloading, e non deve essere confuso
con esso.
L'overloading consente di definire in una stessa classe più metodi aventi lo stesso nome, ma che differiscano nella firma, cioè nella sequenza dei tipi dei parametri formali. È il compilatore che determina quale dei metodi verrà invocato, in base al numero e al tipo dei parametri attuali. L'overriding, invece, consente di ridefinire un metodo in una sottoclasse: il metodo originale e quello che lo ridefinisce hanno necessariamente la stessa firma, e solo a tempo di esecuzione si determina quale dei due deve essere eseguito. |
![]() ![]() |
|
Se nella classe Tempo avessimo dichiarato le variabili d'istanza private, il metodo visualizza di Tempo2 avrebbe causato un errore in compilazione, tentando di accedere a variabili private. Il modificatore protected consente invece l'accesso alle variabili d'istanza a tutte le sottoclassi. |
![]() ![]() |
|
Come sappiamo, la variabile this
fa riferimento, nel corpo di un metodo o di un costruttore, all'oggetto
che lo sta eseguendo.
La variabile super fa riferimento anch'essa all'istanza che sta eseguendo un metodo o un costruttore, ma costringe l'interprete a vedere l'oggetto come istanza della superclasse. Ad esempio, riscriviamo il metodo visualizza
per Tempo2 in mododa riutilizzare il metodo visualizza
di Tempo.
Analogamente, il seguente costruttore per Tempo2
richiama un costruttore per Tempo al suo interno [l'invocazione
di super() deve essere la prima istruzione].
|
![]() ![]() |
|
![]() ![]() |
|