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
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:
Domanda
•
Date due classi Manager e Employee, quale
delle due definireste come superclasse e
quale come sottoclasse?
Risposta
Employer
Manager
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
Domanda
•
Quale è lo scopo della classe
JTextComponent nella gerarchia Swing?
Risposta
•
Implementa gli aspetti comuni del
comportamento delle classi JTextField
e JTextArea
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
con lo stesso nome:
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 anotherAccount 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…
File BankAccount.java
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
/**
La classe radice della gerarchia
*/
public class BankAccount
{
/**
Constructs a bank account with a zero balance.
*/
public BankAccount()
{
balance = 0;
}
/**
Constructs a bank account with a given balance.
@param initialBalance the initial balance
*/
Continua…
File BankAccount.java
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
public BankAccount(double initialBalance)
{
balance = initialBalance;
}
/**
Peposita un importo sul conto.
@param amount l’importo da depositare
*/
public void deposit(double amount)
{
balance = balance + amount;
}
/**
Prelievo di un importo dal conto.
@param amount l’importo da prelevare
*/
Continua…
File BankAccount.java
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
public void withdraw(double amount)
{
balance = balance - amount;
}
/**
Saldo corrente del conto.
@return il valore del campo balance
*/
public double getBalance()
{
return balance;
}
/**
*/
Trasferimento da questo conto ad un altro conto
@param amount l’importo da trasferire
@param other il conto su cui trasferire
Continua…
File BankAccount.java
56:
57:
58:
59:
60:
61:
62:
63: }
public void transfer(double amount, BankAccount other)
{
withdraw(amount);
other.deposit(amount);
}
private double balance;
File CheckingAccount.java
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
/**
Un conto corrente, con commissioni sulle operazioni
*/
public class CheckingAccount extends BankAccount
{
/**
Costruisce un conto con un saldo iniziale.
@param initialBalance il saldo iniziale
*/
public CheckingAccount(double initialBalance)
{
// costruttore della superclasse
super(initialBalance);
// inizializza i campi locali
transactionCount = 0;
}
Continua…
File CheckingAccount.java
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
public void deposit(double amount)
{
transactionCount++;
// deposita invocando il metodo della superclasse
super.deposit(amount);
}
public void withdraw(double amount)
{
transactionCount++;
// preleva invocando il metodo della superclasse
super.withdraw(amount);
}
/**
Deduce le commissioni accumulate e riazzera il
contatore delle transazioni.
Continua…
*/
File CheckingAccount.java
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52: }
public void deductFees()
{
if (transactionCount > FREE_TRANSACTIONS)
{
double fees = TRANSACTION_FEE *
(transactionCount - FREE_TRANSACTIONS);
super.withdraw(fees);
}
transactionCount = 0;
}
private int transactionCount;
private static final int FREE_TRANSACTIONS = 3;
private static final double TRANSACTION_FEE = 2.0;
File SavingsAccount.java
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
/**
Un libretto bancario con interessi fissi.
*/
public class SavingsAccount extends BankAccount
{
/**
Costruisce un libretto con un tasso di interesse.
@param rate il tasso di interesse
*/
public SavingsAccount(double rate)
{
interestRate = rate;
}
/**
Aggiunge gli interessi maturati al conto=.
*/
Continua…
File SavingsAccount.java
18:
19:
20:
21:
22:
23:
24:
25: }
public void addInterest()
{
double interest = getBalance() * interestRate / 100;
deposit(interest);
}
private double interestRate;
File AccountTester.java
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
/**
Test per la classe BankAccount e le sue sottoclassi
*/
public class AccountTester
{
public static void main(String[] args)
{
SavingsAccount momsSavings
= new SavingsAccount(0.5);
CheckingAccount harrysChecking
= new CheckingAccount(100);
momsSavings.deposit(10000);
Continua…
File AccountTester.java
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34: }
momsSavings.transfer(2000, harrysChecking);
harrysChecking.withdraw(1500);
harrysChecking.withdraw(80);
momsSavings.transfer(1000, harrysChecking);
harrysChecking.withdraw(400);
// test per le operazioni di fine mese
momsSavings.addInterest();
harrysChecking.deductFees();
System.out.println("Mom's savings balance = $“
+ momsSavings.getBalance());
System.out.println("Harry's checking balance = $“
+ harrysChecking.getBalance());
}
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:
1. exp = super:
• S è la superclasse della classe in cui
l’invocazione occorre:
2. exp = this:
• S è la classe in cui l’invocazione occorre
3. 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
1. calcola il tipo degli argomenti, a1:S1,.... an:Sn
2. 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)”); }
}
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)) = ={ B.m(String)
}
a.m(1)
// BEST(A,m(int))
{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 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 { . . . }
}
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();
}
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 Coin
{
// sovrascrive il metodo di object
public 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
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:
1. exp = super:
• S è la superclasse della classe in cui
l’invocazione occorre:
2. exp = this:
• S è la classe in cui l’invocazione occorre
3. 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
1. calcola il tipo degli argomenti, a1:S1,.... an:Sn
2. seleziona in S il metodo T m(T1,....,Tn) tale
che Si <:Ti e m() è accessibile dal contesto di
chiamata
3. 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 – Fase 2 rivista
exp.m(a1,..., an)
exp:S
• Determina la firma del metodo da invocare
1. calcola il tipo degli argomenti, a1:S1,.... an:Sn
2. 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)
4. 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();
// APP(A,m(int))
a.m(1)
APP(B, m(int)) = ={A.m(int)}
{ A.m(int) }
b.m(“a string”) ; // APP(B,m(String))= {B.m(String)}
// APP(B,m(int))
= {A.m(int)}
b.m(1);
}
}
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)) =
B.m(String) }
a.m(1)
// APP(A,m(int))
= { {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) {
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(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();
d.n();
System.out.println(d.str); // D
C c = d;
c.m();
// C
((D)c).n();
// D
System.out.println(c.str); // C
// C
// D
Scarica

08Inheritance