Ereditarietà
• Concetti principali
• Ereditarietà e (overriding) di metodi
–
Dynamic dispatch e polimorfismo
• Ereditarietà e costruttori
• Livelli di accesso protected e package
• La classe Object
–
metodi toString, equals e clone
Ereditarietà – introduzione
• Un meccanismo per estendere classi con
nuovi campi e nuovi metodi
• Esempio: dato un conto bancario, vogliamo
definire un conto con interessi
• SavingsAccount (~ libretto di risparmio)
class SavingsAccount extends BankAccount
{
nuovi metodi
nuovi campi
}
Continua…
Ereditarietà – introduzione
• SavingsAccount eredita metodi e campi di
BankAccount
SavingsAccount collegeFund = new SavingsAccount(10);
// Conto con il 10% tasso di interesso
collegeFund.deposit(500);
// Possiamo usare un metodo di BankAccount
// anche su oggetti di tipo SavingsAccount
• Terminologia
–
–
superclasse = classe che viene estesa (BankAccount)
sottoclasse = classe che estende (Savings)
Continua…
Diagrammi UML
SuperClasse
Interfaccia
Classe
Classe
Diagrammi UML
• La relazione di sottoclasse è transitiva
• Ogni class estende la classe Object,
direttamente o indirettamente
Superclassi vs interfacce
• Estendere una classe è diverso da
implementare una interfaccia
• Entrambi i meccanismi generano sottotipi
–
–
Interfacce rappresentano le classi che le
implementano
Classi rappresentano le proprie sottoclassi
• Ma …
–
–
Una sottoclasse eredita l’implementazione dei
metodi ed i campi della superclasse
riuso di codice di implementazione
Ereditarietà – sottoclassi
• Nell’implementazione di una sottoclasse
definiamo solo ciò che cambia rispetto alla
superclasse
Ereditarietà – sottoclassi
• SavingsAccount implementa le differenze
rispetto a BankAccount
public class SavingsAccount extends BankAccount
{
public SavingsAccount(double rate)
{
interestRate = rate;
}
public void addInterest()
{
double interest = getBalance() * interestRate / 100;
deposit(interest);
}
private double interestRate;
}
Domanda
•
Quali metodi possono essere invocati su
oggetti di tipo SavingsAccount?
Risposta
•
Tutti i metodi ereditati:
deposit(), withdraw(), getBalance()
•
Il metodo addInterest() definito nella
sottoclasse
Ereditarietà – this
• addInterest() invoca deposit() senza
specificare un oggetto
• l’invocazione riguarda this, l’istanza della
sottoclasse che eredita i metodi della
superclasse
Ereditarietà – encapsulation
• Le sottoclassi non hanno accesso ai campi
privati della superclasse
• SavingsAccount non può riferire
direttamente il campo balance
• Può usare i metodi ereditati dalla superclasse
–
addInterest() accede a balance con i metodi
getBalance() e deposit()
Istanze di sottoclasse
• Un oggetto di tipo SavingsAccount eredita il
campo balance da BankAccount, ed ha un
campo aggiuntivo, interestRate:
Gerarchie di classi
• Le applicazioni sono spesso formate come
gerarchie complesse di classi in relazione di
ereditarietà
Gerarchie di classi – Swing
Continua…
Gerarchie di classi – Swing
• La superclasse JComponent definisce due
metodi getWidth, getHeight ereditati da
tutte le classi nella gerarchia
• La classe AbstractButton ha metodi
set/get per definire e accedere alla etichetta
o icona associata al pulsante
Una gerarchia di conti bancari
Continua…
Una gerarchia di conti bancari
–
–
–
BankAccount realizza le funzionalità di base di un
conto bancario
CheckingAccount: non offre interessi, ma
permette un numero di transazioni gratuite e per le
altre commissioni basse
SavingsAccount : interessi calcolati su base
mensile e transazioni con commissione
Continua…
Una gerarchia di conti bancari
• Tutti i tipi di conto offrono il metodo
getBalance()
• Tutti i tipi di conto offrono deposit()e
withdraw(),
–
Implementazione diversa nei diversi tipi
• CheckingAccount include un metodo
deductFees() per dedurre le commissioni
• SavingsAccount include un metodo
addInterest() per il calcolo degli interessi
Ereditarietà – metodi
• Tre scelte possibili nel progetto di una
sottoclasse
–
–
–
ereditare i metodi della superclasse
modificare i metodi della superclasse (overriding)
definire nuovi metodi / nuove versioni dei metodi
ereditati
Continua…
Ereditarietà – metodi ereditati
• Metodi della superclasse visibili nella
sottoclasse
• Non ridefiniti nella sottoclasse
• I metodi della superclasse possono essere
sempre invocati su oggetti della sottoclasse
–
–
Quando invocati su oggetti della sottoclasse this
punta all’oggetto della sottoclasse
Il tipo di this cambia, a seconda dell’oggetto a cui
this viene legato
Continua…
Ereditarietà – metodi aggiuntivi
• Le sottoclassi possono definire nuovi
metodi, non definiti nella superclasse
• I nuovi metodi sono invocabili solo su
oggetti della sottoclasse
Ereditarietà – overriding di metodi
• Forniscono una implementazione diversa di
un metodo che esiste nella superclasse
• Il metodo nella sottoclasse deve avere la
stessa firma del metodo nella superclasse
–
–
stesso nome/tipo risultato, stessi tipi dei parametri,
livello di accesso maggiore o uguale
Continua…
Ereditarietà – variabili di istanza
• Tutti i campi di una superclasse sono
automanticamente ereditati nella sottoclasse
–
Ma, se privati, non possono essere acceduti
direttamente dalla sottoclasse!
• La sottoclasse può definire campi che non
esistono nella superclasse
Continua…
Ereditarietà – variabili di istanza
• Nel caso di collisione di nomi …
–
La definizione del campo della sottoclasse maschera
quella del campo nella superclasse
• Collisioni di nomi legali ma decisamente
inopportune (… vedremo)
Una gerarchia di conti bancari
Continua…
La classe CheckingAccount
• Sovrascrive (overrides) i metodi deposit() e
withdraw() per incrementare il contatore
delle operazioni:
public class CheckingAccount extends BankAccount
{
public void deposit(double amount) {. . .}
public void withdraw(double amount) {. . .}
public void deductFees() {. . .} // nuovo metodo
private int transactionCount;
// nuovo campo
}
Continua…
La classe CheckingAccount
• due variabili di istanza:
–
–
Balance, ereditato da BankAccount
transactionCount, nuovo
Continua…
La classe CheckingAccount
• Quattro metodi
–
–
–
–
getBalance()
●
ereditato da BankAccount
deposit(double amount)
●
sovrascrive il metodo corrispondente di
BankAccount
withdraw(double amount)
●
sovrascrive il metodo corrispondente di
BankAccount
deductFees()
●
nuovo
Accesso ai campi della superclasse
• Consideriamo il metodo deposit() della classe
CheckingAccount
public void deposit(double amount)
{
transactionCount++;
// aggiungi amount a balance
. . .
}
Continua…
Accesso ai campi della superclasse
• Consideriamo il metodo deposit() della classe
CheckingAccount
public void deposit(double amount)
{
transactionCount++;
// aggiungi amount a balance
balance += amount;// ERRORE!
}
• Non possiamo modificare direttamente balance
–
balance è un campo privato della superclasse
Continua…
Accesso ai campi della superclasse
• Una sottoclasse non ha accesso ai campi /
metodi private della superclasse
• Deve utilizzare l’interfaccia public
(o protected vedremo …) della superclasse
• Nel caso in questione, CheckingAccount deve
invocare il metodo deposit() della superclasse
Accesso ai campi della superclasse
• Errore tipico: aggiungiamo un nuovo campo
AI!stesso nome:
Mlo
con
public class CheckingAccount extends BankAccount
{
public void deposit(double amount)
{
transactionCount++;
balance = balance + amount;
}
. . .
private double balance; // Brutta idea!
}
Continua…
Accesso ai campi della superclasse
• Ora il metodo deposit compila correttamente
ma non modifica il saldo corretto!
Continua…
Accesso ai campi della superclasse
• Soluzione, accediamo i campi della superclasse
via i metodi della stessa superclasse
public class CheckingAccount extends BankAccount
{
public void deposit(double amount)
{
transactionCount++;
balance = balance + amount;
deposit(amount);
}
. . .
}
Continua…
Chiamata di un metodo overridden
• Non possiamo invocare direttamente
deposit(amount) ….
class CheckingAccount public {
. . .
void deposit(double amount)
{
transactionCount++;
balance = balance + amount;
deposit(amount); // questo è un loop!
}
. . .
}
• È come invocare this.deposit(amount)
Continua…
Chiamata di un metodo overridden
• Dobbiamo invece invocare il metodo
deposit() della superclasse
• Invochiamo via super
public void deposit(double amount)
{
transactionCount++;
balance = balance + amount;
super.deposit(amount);
}
Sintassi: chiamata via super
super.methodName(parameters)
Esempio:
public void deposit(double amount)
{
transactionCount++;
super.deposit(amount);
}
Scopo:
Invocare un metodo overridden della superclasse
La classe CheckingAccount
public class CheckingAccount extends BankAccount
{
. . .
public void withdraw(double amount)
{
transactionCount++;
// sottrai amount da balance
super.withdraw(amount);
}
Continua…
La classe CheckingAccount
• Definizione dei metodi aggiuntivi
public void deductFees()
{
if (transactionCount > FREE_TRANSACTIONS)
{
double fees = TRANSACTION_FEE
* (transactionCount - FREE_TRANSACTIONS);
super.withdraw(fees);
}
transactionCount = 0;
}
. . .
private static final int FREE_TRANSACTIONS = 3;
private static final double TRANSACTION_FEE = 2.0;
}
Domanda
•
Perchè il metodo withdraw() nella classe
CheckingAccount invoca
super.withdraw()?
•
È possibile invocare un metodo della
supeclasse senza utilizzare il riferimento
super?
•
Perchè il metodo deductFees() nella
classe CheckingAccount invoca
super.withdraw()?
Risposte
•
Perché deve modificare la variabile
balance e non può farlo direttamente in
quanto è privata della superclasse n
•
Si se la sottoclasse non ridefinisce il
metodo
•
Per evitare che il prelievo dell’importo delle
commissioni venga contato come
operazione.
Ereditarietà
(ancora)
Costruttori di sottoclasse
• Il costruttore di una sottoclasse provvede ad
inizializzare la struttura delle istanze della
sottoclasse
• Come abbiamo visto, questa include la parte
definita nella superclasse
• Per inizializzare i campi privati della
superclasse invochiamo il costruttore
della superclasse
Continua…
Istanze di sottoclasse
• Un oggetto di tipo SavingsAccount eredita il
campo balance da BankAccount, ed ha un
campo aggiuntivo, interestRate:
Costruttori di sottoclasse
• Per invocare il costruttore della superclasse dal
costruttore di una sottoclasse usiamo la parola
chiave super seguita dagli argomenti
• Deve essere il primo comando del costruttore
della sottoclasse
Continua…
Costruttori di sottoclasse
class SavingsAccount extends BankAccount
{
public SavingsAccount(double balance, double ir)
{
// Chiamata al costruttore di superclasse
super(balance);
// inizializzazioni locali
interestRate = ir;
}
. . .
}
• NB: non confondere con la chiamata di
metodo della superclasse super.m(…)
Continua…
Costruttori di sottoclasse
• Se il costruttore di sottoclasse non invoca
esplicitamente il costruttore di superclasse,
il compilatore inserisce la chiamata super()
al costuttore di default (senza parametri)
–
Se tutti i costruttori della superclasse richiedono
parametri, errore di compilazione
Conversioni di tipo
• Una sottoclasse è un sottotipo della sua
superclasse
• Possiamo assegnare ad una variabile di
classe un riferimento di tipo sottoclasse
SavingsAccount collegeFund = new SavingsAccount(10);
BankAccount anAccount = collegeFund;
Object anObject = collegeFund;
Conversioni di tipo
• I tre riferimenti contenuti in collegeFund,
anAccount e anObject riferiscono tutti lo
stesso oggetto, di tipo SavingsAccount
Conversioni di tipo
• Utilizzare un riferimento di tipo superclasse
causa una perdita di informazione
BankAccount anAccount = new SavingsAccount(10);
anAccount.deposit(1000); // OK
// deposit() e’ un metodo di BankAccount
anAccount.addInterest(); // Compiler Error
// addInterest() non e’ un metodo di BankAccount
Cast
• Possiamo recuperare informazione
utilizzando cast
BankAccount anAccount = new SavingsAccount(10);
anAccount.deposit(1000); // OK
// deposit() e’ un metodo di BankAccount
((SavingsAccount)anAccount).addInterest(); // OK
• Attenzione:
–
se anAccount non punta ad un SavingsAccount
ClassCastException a run
Conversioni di tipo
• Soluzione: utilizzo dell’operatore
instanceof
if (anAccount instanceof SavingsAccount)
{
((SavingsAccount)anAccount).addInterest(); // OK
. . .
}
• verifica se anAccount punta ad un oggetto
di tipo SavingsAccount (o di un sottotipo)
Conversioni di tipo
• Nulla di nuovo …
–
stesse idee e meccanismi visti quando abbiamo
parlato delle interfacce.
Polimorfismo
public void transfer(double amount, BankAccount other)
{
withdraw(amount);
other.deposit(amount);
}
• Questo codice può essere utilizzato per
trasferimenti su qualunque BankAccount
Polimorfismo – dynamic dispatch
• Meccanismo già discusso per le interfacce
• Ogni variabile ha due tipi
–
–
Statico: il tipo dichiarato
Dinamico: il tipo dell’oggetto a cui la variabile
riferisce
• Il metodo invocato da un messaggio dipende
dal tipo dinamico della variabile, non dal tipo
statico
Continua…
Polimorfismo – dynamic dispatch
• Il compilatore verifica che esista un metodo
da selezionare in risposta al messaggio
–
–
se non esiste errore
se esiste decide la firma del metodo da invocare
• Il corpo del metodo, con il tipo selezionato,
viene determinato a run time.
Polimorfismo – dynamic dispatch
• Dynamic dispatch al lavoro
public void transfer(double amount, BankAccount other)
{
withdraw(amount);
// this.withdraw(amount)
other.deposit(amount);
}
• La selezione (dispatch) dei metodi
withdraw() e deposit() da eseguire
dipende dal tipo dinamico di this e di
other, rispettivamente
Continua…
Polimorfismo – dynamic dispatch
public void transfer(double amount, BankAccount other)
{
withdraw(amount);
// this.withdraw(amount)
other.deposit(amount);
}
BankAccount sa = new SavingsAccount(10);
BankAccount ca = new CheckingAccount();
sa.transfer(1000, ca);
• invoca
–
–
SavingsAccount.withdraw() su sa
CheckingAccount.deposit() su ca
Continua…
Invocazione di metodi
• Due fasi nella selezione del metodo da
invocare:
• Fase statica:
–
–
seleziona il tipo del metodo, in funzione del tipo
statico degli argomenti presenti nel messaggio
risoluzione dell’overloading
• Fase dinamica: dispatch
–
seleziona il corpo del metodo, in funzione del tipo
dinamico del destinatario dell’invocazione
Selezione statica
exp.m(a1,..., an)
Due fasi:
1. Determina il tipo di exp
2. Seleziona la firma T m(T1,…Tn) del
metodo da invocare in base al tipo degli
argomenti a1,...,an
In questa fase (statica)
•. tipo = tipo statico
Selezione Statica / destinatario
exp.m(a1,..., an)
• Determina il tipo statico S di exp:
–
–
–
exp = super:
●
S è la superclasse della classe in cui
l’invocazione occorre:
exp = this:
●
S è la classe in cui l’invocazione occorre
in tutti gli altri casi:
●
S è il tipo dichiarato per exp
Selezione statica / metodo
exp.m(a1,..., an)
exp:S
• Determina la firma del metodo da
invocare
–
–
calcola il tipo degli argomenti, a1:S1,....
an:Sn
determina il best match m(T1,...,Tn) per
l’invocazione m(a1:S1,… an:Sn), a partire da
S
•. Risoluzione overloading
–
–
se trovi una sola firma: ok
altrimenti se nessuno / più di best match: errore
Esempio
class A {
public void m(double d){System.out.println("A.m(double)");
public void m(int i) {
System.out.println(“A.m(int)"); }
}
class B extends A {
public void m(double d){System.out.print(“B.m(double)”); }
public void k(double d){System.out.print(“B.m(double)”); }
}
class over {
public static void main(String[] args) {
{ A a = new B();
a.m(1.5); }
}
// Selezione statica: BEST(A, m(double)) = { A.m(double) }
// Dispatch: B ridefinisce m(double). Quindi esegui B.m(double)
Esempio
class A {
public void m(double d){ System.out.println("A.m(double)");
public void m(int i) {
System.out.println(“A.m(int)"); }
}
class B extends A {
public void m(double d){System.out.print(“B.m(double)”); }
}
class over {
public static void main(String[] args) {
{ A a = new B();
a.m(1); }
}
// Selezione statica: BEST(A, m(int)) = { A.m(int) }
// Dispatch: B non ridefinisce m(int). Quindi esegui A.m(int)
Esempio
class A {
public void m(double d){ System.out.println("A.m(double)");
public void m(int i) {
System.out.println(“A.m(int)"); }
}
class B extends A {
public void m(double d){System.out.print(“B.m(double)”); }
}
class over {
public static void main(String[] args) {
{ B b = new B();
b.m(1); }
}
// Selezione statica: BEST(B, m(int)) = { A.m(int) }
// Dispatch: B non ridefinisce m(int). Quindi esegui A.m(int)
Esempio
class A {
public void m(int i)
{ System.out.println("A.m(int)"); }
}
class B extends A {
public void m(String s)
{ System.out.println("B.m(String)"); }
}
class over {
public static void main(String[] args) {
B b = new B();
APP(A, m(int))
= { A.m(int) }
A a = new B();
APP(B, m(String)) =
= {{A.m(int)}
B.m(String) }
a.m(1)
// BEST(A,m(int))
b.m(“a string”) //
; BEST(B,m(String))=
APP(A, m(String)) = {{B.m(String)}
}
a = b;
a.m(“a string”);// BEST(A,m(string))= {}
}
}
Esempio
class A {
public void m(int i, float f) { /* just return */}
public void m(float f, int i) { /* just return */}
}
class test {
public static void main(String[] args) {
A a = new A();
a.m(1, 1);
}
// BEST(A,m(int,int)) = { A.m(int,float), A.m(float,int) }
Ereditarietà
(ultima)
Classi astratte
• Un ibrido tra classi e interfacce
• Hanno alcuni metodi implementati
normalmente, altri non implementati (astratti)
–
Un metodo astratto non ha implementazione
abstract class AccountTransaction
{
. . .
public abstract void esegui();
. . .
}
–
Il metodo, e la classe sono abstract
Classi astratte
• Le classi astratte possono comunque avere
costruttori
–
invocabili solo dai costruttori di sottoclasse
Classi astratte
abstract class AccountTransaction
{
private BankAccount acct;
public AccountTransaction(BankAccount a)
{
acct = a ;
}
public BankAccount getAcct()
{
return acct;
}
public abstract void esegui();
}
Sottoclassi concrete
class Prelievo extends AccountTransaction
{
private double importo;
public Prelievo(BankAccount a, double i)
{
super(a);
importo = i;
}
public void esegui()
{
getAcct().preleva(i);
}
}
Sottoclassi concrete
• Le classi che estendono una classe astratta
DEVONO implementare (sovrascrivere) tutti I
metodi astratti
–
altrimenti anche le sottoclassi della classe astratta
sono astratte
Classi astratte
• E’ possibile dichiarare astratta una classe
priva di metodi astratti
–
In questo modo evitiamo che possano essere costruiti
oggetti di quella classe
Eventi del mouse
abstract class MouseAdapter implements MouseListener
{
// un metodo per ciascun mouse event su una componente
public void mousePressed(MouseEvent event) {/* donothing */};
public void mouseReleased(MouseEvent event){/* donothing */};
public void mouseClicked(MouseEvent event) {/* donothing */};
public void mouseEntered(MouseEvent event) {/* donothing */};
public void mouseExited(MouseEvent event)
{/* donothing */};
}
Continua…
Eventi del mouse
• MouseAdapter è abstract e quindi non può
essere istanziata direttamente, anche se tutti
i suoi metodi sono concreti
• possiamo estenderla con un classe concreta
class MyAdapter extends MouseAdapter
{
/* gestisce solo gli eventi “click” */
public void MouseClicked { . . . }
}
Object: la superclasse cosmica
• Tutte le classi definite senza una clausola extends
esplicita estendono automaticamente la classe
Object
Object: la superclasse cosmica
•
I metodi più utili definiti da questa classe
–
–
–
•
String toString()
boolean equals(Object otherObject)
Object clone()
Spesso overridden nelle classi di sistema e/o
nelle classi definite da utente
toString()
• Object.toString() restituisce una stringa
ottenuta dalla concatenazione del nome della
classe seguita dal codice hash dell’oggetto
su cui viene invocato.
Continua…
toString()
• invocato automaticamente tutte le volte che
concateniamo un oggetto con una stringa
• ridefinito in tutte le classi predefinite per
fornire una rappresentazione degli oggetti
come stringhe
Rectangle box = new Rectangle(5, 10, 20, 30);
String s = box.toString();
// s = "java.awt.Rectangle[x=5,y=10,width=20,height=30]"
Continua…
toString()
• Possiamo ottenere lo stesso effetto nelle
classi user-defined, ridefinendo il metodo:
public String toString()
{
return "BankAccount[balance=" + balance + "]";
}
new BankAccount(5000).toString();
// restituisce "BankAccount[balance=5000]"
equals()
• nella classe Object coincide con il test ==
–
verifica se due riferimenti sono identici
• sovrascritto in tutte le classi predefinite per
implementare un test di uguaglianza di stato
• Il medesimo effetto si ottiene per le classi
user-defined, sempre mediante overriding
• La firma del metodo in Object:
public boolean equals(Object otherObject)
Continua…
Esempio
• Monete e monete da collezione
class Coin
{
private double value;
private String name,
. . .
}
class CollectibleCoin extends Coin
{
private int year;
. . .
}
Monete uguali
public
{
class
//
public
Coin
sovrascrive
il
metodo
di
object
boolean
equals(Object
otherObject)
{
if
(otherObject
==
null)
return
false;
Coin
other
=
(Coin)
otherObject;
return name.equals(other.name) && value == other.value;
}
.
.
.
}
NOTE
●
●
cast per recuperare informazione sul parametro
equals per confrontare campi riferimento
Continua …
Monete uguali
public class Coin
{
. . .
public boolean equals(Object otherObject)
{
if (otherObject == null) return false;
Coin other = (Coin) otherObject;
return name.equals(other.name) && value == other.value;
}
. . .
}
• Se otherObject non è un Coin, errore ...
Continua …
Monete uguali
public class Coin
{
// nuova versione del metodo equals
public boolean equals(Coin other)
{
if (other == null) return false;
return name.equals(other.name) && value == other.value;
}
// sovrascrive il metodo equals di object
public boolean equals(Object other)
{
if (other == null) return false;
if (other instanceof Coin) return equals((Coin)other);
return false;
}
cast forza l’invocazione
. . .
della versione corretta
}
Monete uguali
• Nelle sottoclassi stessa logica …
class CollectibleCoin extends Coin {
{
public boolean equals(CollectibleCoin other) { . . . }
public boolean equals(Object other)
{
if (other == null) return false;
if (other instanceof CollectibleCoin)
return equals((CollectibleCoin)other);
return false;
}
. . .
Monete uguali
• … ma attenzione alla struttura ereditata!
public boolean equals(CollectibleCoin other)
{
if (!super.equals(other)) return false;
return year == other.year;
}
Domanda
Quale versione di equals nella classe
coin invoca questa chiamata?
public boolean equals(CollectibleCoin other)
{
if (!super.equals(other)) return false;
return year == other.year;
}
private int year;
}
Risposta
• La versione con il parametro di tipo Coin
●
other:CollectibleCoin è anche un Coin
Domanda
•
Cosa dobbiamo aspettarci dalla chiamata
x.equals(x)? Deve sempre restituire
true?
Risposta
•
Si, a meno che, ovviamente, x non sia il
riferimento null.
clone()
• Come ben sappiamo, l’assegnamento tra due
riferimenti crea due riferimenti che puntano
allo stesso oggetto
BankAccount account2 = account;
Continua…
clone()
• Talvolta è utile/necessario creare una copia
dell’intero oggetto, non del solo riferimento
Object.clone()
• Crea shallow copies (copie superficiali)
Continua…
Object.clone()
• Non ripete il cloning sistematicamente sui campi di
tipo riferimento
• Dichiarato protected per proibirne l’invocazioni su
oggetti la cui classe non abbia ridefinito
esplicitamente clone() con livello di accesso
public
• Controlla che l’oggetto da clonare implementi
l’interfaccia Cloneable
• La ridefinizione del metodo nelle sottoclassi richiede
attenzione (vedi API)
clone() – overriding
• Una classe che ridefinisce clone() deve
attenersi alla firma del metodo nella classe
Object
Object clone()
• L’uso del metodo che ne deriva è:
BankAccount cloned = (BankAccount) account.clone();
• Necessario il cast perchè il metodo ha
Object come tipo risultato
Metodi e (sotto) classi final
• È possibile impedire la ridefinizione di un
metodo in una sottoclasse, dichiarando il
metodo final
• Oppure dichiarare non ridefinibili tutti i metodi
della classe, definendo la classe stessa final
Continua…
Metodi e (sotto) classi final
• Due motivazioni
–
–
Efficienza: metodi final hanno dispatch statico
Sicurezza
public class SecureAccount extends BankAccout
{
public final boolean checkPassword(String pwd)
{ . . . }
}
Continua…
Controllo degli accessi
• Java ha quattro livelli di accessibilità per gli
elementi di una classe
–
–
–
–
public
●
accessibile dai metodi di qualunque classe
private
●
accessibile solo dai metodi della proria classe
protected
●
accessibile alle sottoclassi
package
●
accessibile da tutte le classi dello stesso package
●
Il livello default, quando non specifichiamo alcun
livello di accesso in modo eseplicito
Protected
abstract class AccountTransaction
{
private BankAccount acct;
protected AccountTransaction(BankAccount a)
{
acct = a ;
}
protected BankAccount getAcct()
{
return acct;
}
public abstract void esegui();
}
Overriding vs overloading
• Notare che esistono due fasi nella selezione
del metodo da invocare:
• Fase statica: risoluzione dell’overloading
–
determina il tipo del metodo, in funzione del tipo
statico degli argomenti presenti nel messaggio
• Fase dinamica: dispatch
–
determina il corpo del metodo, in funzione del tipo
dinamico del parametro implicito (ovvero, del
destinatario del messaggio)
Polimorfismo – run time
• Dispatch articolato di quanto abbiamo visto …
• Metodi
–
–
dispatch dinamico, con le seguenti eccezioni:
metodi private, chiamate via super
• Metodi di classe (static)
–
dispatch è sempre statico
• Campi
–
dispatch è sempre statico, sia per variabili di istanza,
sia per variabili di classe (o static)
Continua…
Invocazione di metodi
• exp.m(a1,..., an)
–
per definire precisamente l’effetto della chiamata
dobbiamo analizzare tre aspetti:
• selezione statica
–
–
decide se è corretto invocare m(…) su exp, ovvero
se esiste un metodo da invocare
determina il tipo del metodo da invocare
• dispatch
–
determina il corpo del metodo da invocare
Selezione statica
exp.m(a1,..., an)
Due fasi:
1. Determina il tipo di exp
2. Determina la firma T m(T1,…Tn) del
metodo da invocare in base al tipo degli
argomenti a1,...,an
In questa fase (statica)
•. tipo = tipo statico
Selezione Statica – Fase 1
exp.m(a1,..., an)
• Determina il tipo statico S di exp:
–
–
–
exp = super:
●
S è la superclasse della classe in cui
l’invocazione occorre:
exp = this:
●
S è la classe in cui l’invocazione occorre
in tutti gli altri casi:
●
S è il tipo dichiarato per exp
Selezione statica – Fase 2
exp.m(a1,..., an)
exp:S
• Determina la firma del metodo da
invocare
–
–
–
calcola il tipo degli argomenti, a1:S1,....
an:Sn
seleziona in S il metodo T m(T1,....,Tn) tale
che Si <:Ti e m() è accessibile dal contesto di
chiamata
se S non ha un metodo m() con le caratteristiche
desiderate, ripeti il passo 2 sul supertipo di S
(ognuno dei supertipi di S ), finché non trovi un
metodo oppure esaurisci la gerarchia.
Selezione statica e overloading
• L’algoritmo appena visto assume che ogni classe
contenga al più una versione del metodo da invocare
• Che succede se esiste una classe contiene più di una
versione?
• Che succede se una classe ed una superclasse
contengono diverse versioni dello stesso metodo?
• Sono entrambi casi di overloading, e la selezione statica
deve risolverlo
• Selezione statica richiede overloading resolution
Selezione statica – Fase 2 rivista
exp.m(a1,..., an)
exp:S
• Determina la firma del metodo da
invocare
–
–
calcola il tipo degli argomenti, a1:S1,....
an:Sn
determina il best match m(T1,...,Tn) per
l’invocazione m(a1:S1,… an:Sn), a partire da
S
•. Se trovi una sola firma ok, altrimenti
errore
Esempio
class A {
public void m(double d){System.out.println("A.m(double)");
public void m(int i) {
System.out.println(“A.m(int)"); }
}
class B extends A {
public void m(double d){System.out.print(“B.m(double)”); }
}
class over {
public static void main(String[] args) {
{ A a = new B();
a.m(1.5); }
}
// Selezione statica: BEST(A, m(double)) = { A.m(double) }
// Dispatch: B ridefinisce m(double). Quindi esegui B.m(double)
Esempio
class A {
public void m(double d){ System.out.println("A.m(double)");
public void m(int i) {
System.out.println(“A.m(int)"); }
}
class B extends A {
public void m(double d){System.out.print(“B.m(double)”); }
}
class over {
public static void main(String[] args) {
{ A a = new B();
a.m(1); }
}
// Selezione statica: BEST(A, m(int)) = { A.m(int) }
// Dispatch: B non ridefinisce m(int). Quindi esegui A.m(int)
Esempio
class A {
public void m(double d){ System.out.println("A.m(double)");
public void m(int i) { System.out.println(“A.m(int)"); }
}
class B extends A {
public void m(double d){System.out.print(“B.m(double)”); }
}
class over {
public static void main(String[] args) {
{ B b = new B();
b.m(1); }
}
// Selezione statica: BEST(A, m(int))={A.m(int),B.m(double)}
Metodi private : dispatch statico
• Essendo private , non sono accessibili alle
sottoclassi.
• Quindi le sottoclassi non possono fare
overriding di questi metodi
• Dispatch può essere deciso dal compilatore
Continua…
Best Match
exp.m(a1:S1,..., an:Sn) exp:S
1. Determina l’insieme dei metodi applicabili
APP(S, m(S1, ..., Sn)) =
{U1.m(T11,...,T1n),...,Uk.m(Tk1,...,Tkn)}
tali che, per ogni j in [1..k]
– S <: Uj (quindi: esamina S ed i supertipi di S)
– m(Tj1, …. Tjn) è definito in Uj ed è visibile
nel punto della chiamata exp.m(a1,…,an).
– Si <: Tji per ogni i in [1..n]
2. Se l’insieme è vuoto fallisci
Best Match
3. Altrimenti, calcola l’insieme dei metodi migliori
BEST(S,m(a1:S1,…,. an:Sn))
rimuovi da APP(S, m(a1:S1, ... an:Sn)) ogni
Up.m(Tp1, ..., Tpn) tale che esiste un metodo
migliore, ovvero un metodo Uq.m(Tq1,...,Tqn) tale che
- Uq <:Up (è definito in una superclasse più vicina a S)
- Tqi <: Tpi (ha tipi degli argomenti piu vicini agli Si)
5. Se BEST(S,m(a1:S1,…,an:Sn)) contiene più di
un metodo, fallisci.
Altrimenti l’unico metodo nell’insieme e` il best match per
la chiamata exp.m(a1,...,an)
Best Match – Esempi
class A {
public void m(int i)
{ System.out.println("A.m(int)"); }
}
class B extends A {
public void m(String s)
{ System.out.println("B.m(String)"); }
}
class over {
public static void main(String[]
args)
{
APP(A,
m(int))
= { A.m(int) }
B b = new B();
APP(B, m(String)) = { B.m(String) }
A a = new B();
a.m(1)
// APP(A,m(int))
APP(B, m(int)) = {A.m(int)}
= { A.m(int) }
b.m(“a string”) ; // APP(B,m(String))= {B.m(String)}
b.m(1);
// APP(B,m(int))
= {A.m(int)}
}
}
Best Match – Esempi
class A {
public void m(int i)
{ System.out.println("A.m(int)"); }
}
class B extends A {
public void m(String s)
{ System.out.println("B.m(String)"); }
}
class over {
public static void main(String[] args) {
B b = new B();
APP(A, m(int))
= { A.m(int) }
A a = new B();
APP(B, m(String))== {A.m(int)}
{ B.m(String) }
a.m(1)
// APP(A,m(int))
b.m(“a string”) ;// APP(B,m(String))=
APP(A, m(String)) = {B.m(String)}
{}
a = b;
a.m(“a string”); // APP(A,m(string))= {}
}
}
Best Match – Esempi
class A {
public void m(int i) { System.out.println("A.m(int)"); }
}
class B extends A {
public void m(double f) {
}
class over {
public static
B b = new
A a = new
a.m(1); //
b.m(1.5);
//
b.m(1); //
}
//
}
System.out.println("B.m"); }
void main(String[] args) {
B();
B();
APP(A,m(int))
=
APP(B,m(double))=
APP(B,m(int))
=
BEST(B.m(int)) =
{A.m(int)}
{B.m(double)}
{A.m(int),B.m(double)}
{A.m(int),B.m(double)}
Best Match – Esempi
class A {
public void m(double g) { System.out.println("A.m"); }
}
class B extends A {
public void m(int i) {
System.out.println("B.m"); }
}
class over {
public static void main(String[]
B b = new B();
A a = new B();
a.m(1); // APP(A,m(int))
=
b.m(1.5);
// APP(B,m(double))=
b.m(1); // APP(B,m(int))
=
}
// BEST(B.m(int)) =
}
args) {
{A.m(double)}
{A.m(double)}
{A.m(double),B.m(int)}
{B.m(int)}
Best Match – Esempi
class A {
public void m(int i, float f) { /* just return */}
public void m(float f, int i) { /* just return */}
}
class test {
public static void main(String[] args) {
A a = new A();
a.m(1, 1);
}
// APP(A, m(int,int)) = { A.m(int,float), A.m(float,int) }
// BEST(A,m(int,int)) = { A.m(int,float), A.m(float,int) }
Metodi private : dispatch statico
dispatch statico:
A.sd()
class A {
public String test() { return this.sd() + " , " + this.dd(); }
private String sd(){ return "A.sd()"; }
dispatch dinamico:
public String dd() { return "A.dd()"; }
risolto a run time
}
class B extends A
public String
public String
}
. . .
// new B().test()
// new A().test()
{
sd() { return "B.sd()"; }
dd() { return "B.dd()"; }
= “A.sd(), B.dd()”
= “A.sd(), A.dd()”
Chiamate via super: dispatch statico
class A {
public String test() { return dd(); }
public String dd() { return "A.dd()"; }
}
class B extends A {
public String dd(){ return (super.dd() + " , " + "B.dd()"); }
}
. . .
// super.dd() invoca sempre A.dd()
// new B().test() = “A.dd(), B.dd()”
// new A().test() = “A.dd()”
Campi: dispatch statico
class C {
String str = "C";
public void m() { System.out.println(str); }
}
class D extends C {
String str = "D";
public void n() { System.out.println(str); }
}
. . .
D d = new D();
d.m();
// C
d.n();
// D
System.out.println(d.str); // D
C c = d;
c.m();
// C
((D)c).n();
// D
System.out.println(c.str); // C
Scarica

Slides