• Concetti principali
• Ereditarietà e (overriding) di metodi
Dynamic dispatch e polimorfismo
Ereditarietà
• Ereditarietà e costruttori
• Livelli di accesso protected e package
• La classe Object
metodi toString, equals e clone
Ereditarietà – introduzione
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
}
• SavingsAccount eredita automaticamente tutti i
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…
Superclassi vs interfacce
Continua…
Superclassi vs interfacce
• Estendere una classe è diverso da
implementare una interfaccia
• Estendere una classe è diverso da
implementare una interfaccia
• Elementi comuni: entrambi i meccanismi
generano sottotipi
• Elementi di differenza:
Istanze di una sottoclasse possono essere assegnate
a variabili della superclasse
Istanze di una classe possono essere asseganate a
variabili dell’interfaccia che la classe implementa
Estendendo una classe ereditiamo anche il
comportamento (l’implementazione dei metodi ed i
campi) della superclasse
• riuso di codice
È possibile estendere al più una classe (in Java)
mentre si possono implementare più interfacce
Continua…
1
Diagrammi UML
Ereditarietà – introduzione
• Nell’implementazione di una sottoclasse
definiamo solo i campi aggiuntivi, ed i metodi
aggiuntivi o modificati (overridden)
• La relazione di sottoclasse è
transitiva
• Ogni class estende la classe
Object, direttamente o
indirettamente
public class SavingsAccount extends BankAccount
{
public SavingsAccount(double rate)
{
In questo caso solo
interestRate = rate;
campi e metodi nuovi
}
public void addInterest()
{
double interest = getBalance() * interestRate / 100;
deposit(interest);
}
private double interestRate;
}
Ereditarietà – introduzione
Istanze di sottoclasse
• Un oggetto di tipo SavingsAccount eredita il
campo balance da BankAccount, ed ha un
campo aggiuntivo, interestRate:
• Encapsulation: la sottoclasse non ha accesso ai
campi privati della superclasse
addInterest() invoca getBalance() e
deposit() per modificare il saldo
il campo balance non può essere riferito essendo
private)
• Nota: addInterest() invoca getBalance()
senza specificare un oggetto
l’invocazione riguarda this, l’istanza della sottoclasse
che eredita anche la struttura della superclasse
Sintassi: estensione di classe
class SubclassName extends SuperclassName
{
metodi
campi
}
Continua…
Sintassi: estensione di classe
Esempio:
public class SavingsAccount extends BankAccount
{
public SavingsAccount(double rate)
{
interestRate = rate;
}
public void addInterest()
{
double interest = getBalance() * interestRate / 100;
deposit(interest);
}
private double interestRate;
}
Scopo:
Definire una nuova classe che eredita da una classe esistente e definisce
nuovi metodi e campi.
2
Domanda
•
Quali metodi possono essere invocati su
oggetti di tipo SavingsAccount?
Domanda
•
Date due classi Manager e Employee, quale
delle due definireste come superclasse e
quale come sottoclasse?
Gerarchie di classi
Risposta
•
deposit, withdraw, getBalance, e
addInterest.
Risposta
• Manager è la sottoclasse; Employee la
superclasse
Gerarchie di classi – Swing
• Le applicazioni sono spesso formate come
gerarchie complesse di classi in relazione di
ereditarietà
• Esempio:
Continua…
3
Gerarchie di classi – Swing
Domanda
• La superclasse JComponent definisce due
metodi getWidth, getHeight ereditati da
tutte le classi nella gerarchia
•
Quale è lo scopo della classe
JTextComponent nella gerarchia Swing?
• La classe AbstractButton ha metodi
set/get per definire e accedere alla etichetta
o icona associata al pulsante
Risposta
•
Una gerarchia di conti bancari
Rappresenta gli aspetti comuni del
comportamento delle classi JTextField e
JTextArea
• Progettiamo una applicazione per una banca
che voglia offrire ai suoi clienti due tipi di
conti bancari:
Checking account: non offre interessi, ma permette
un numero di transazioni gratuite e per le altre
commissioni basse
Savings Account: interessi calcolati su base mensile
e transazioni con commissione
Continua…
Una gerarchia di conti bancari
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
Continua…
4
Ereditarietà – metodi
Ereditarietà – metodi ereditati
• Tre scelte possibili nel progetto di una
sottoclasse
• Metodi della superclasse visibili nella
sottoclasse
ereditare i metodi della superclasse
modificare i metodi della superclasse (overriding)
definire nuovi metodi / nuove versioni dei metodi
ereditati (overloading)
• 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…
Continua…
Ereditarietà – metodi aggiuntivi
Ereditarietà – overriding di metodi
• Le sottoclassi possono definire nuovi
metodi, non definiti nella superclasse
• Forniscono una implementazione diversa di
un metodo che esiste nella superclasse
• I nuovi metodi sono invocabili solo su
oggetti della sottoclasse
• 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
• Deve anche soddisfare lo stesso contratto
(vedremo …)
Continua…
Domanda
Ereditarietà – overriding di metodi
• Invocazione basata su dispatch dinamico
• Se diretto ad un oggetto della sottoclasse, un
messaggio invoca sempre il metodo della
sottoclasse (come ci si aspetta)
Continua…
• Date le due dichiarazioni
CheckingAccount c = new CheckingAccount(10);
SavingsAccount s = new SavingsAccount();
• Quali dei seguenti comandi compilano
correttamente? Per quelli che compilano
quale è l’effetto dell’esecuzione?
BankAccount b1 = c;
BankAccount b2 = s;
b1.deductFees();
c.deductFees();
s.deductFees();
((CheckingAccount)b1).deductFees();
b1 = b2;
((CheckingAccount)b1).deductFees();
Continued…
5
Risposta
Ereditarietà – variabili di istanza
• Tutti i campi di una superclasse sono
automanticamente ereditati nella sottoclasse
CheckingAccount c = new CheckingAccount(10);
SavingsAccount s = new SavingsAccount();
BankAccount b1 = c; // compila, esegue l’assegnamento
BankAccount b2 = s; // compila, esegue l’assegnamento
b1.deductFees();
// errore di compilazione
c.deductFees();
// compila, invoca il metodo
s.deductFees();
// errore di compilazione
// compila, esegue il metodo
((CheckingAccount)b1).deductFees();
// compila, esegue l’assegnamento
b1 = b2;
// compila ma ClassCastException in esecuzione
((CheckingAccount)b1).deductFees();
Ma non necessariamente 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)
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:
La classe CheckingAccount
• Quattro metodi
Balance, ereditato da BankAccount
transactionCount, nuovo
getBalance()
• ereditato da BankAccount
deposit(double amount)
• sovrascrive il metodo corrispondente di BankAccount
withdraw(double amount)
• sovrascrive il metodo corrispondente di BankAccount
deductFees()
• nuovo
Continua…
6
Accesso ai campi della superclasse
• Consideriamo il metodo deposit() della classe
CheckingAccount
public void deposit(double amount)
{
transactionCount++;
// aggiungi amount a balance
. . .
}
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…
Continua…
Accesso ai campi della superclasse
Accesso ai campi della superclasse
• Una sottoclasse non ha accesso ai campi /
metodi private della superclasse
• Errore tipico: aggiungiamo un nuovo campo
con lo stesso nome:
• Deve utilizzare l’interfaccia public
(o protected vedremo …) della superclasse
• Nel caso in questione, CheckingAccount deve
invocare il metodo deposit() della superclasse
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
Accesso ai campi della superclasse
• Ora il metodo deposit compila correttamente
ma non modifica il saldo corretto!
• 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…
Continua…
7
Chiamata di un metodo overridden
• Non possiamo invocare direttamente
deposit(amount) ….
Chiamata di un metodo overridden
• Dobbiamo invece invocare il metodo
deposit() della superclasse
• Invochiamo via super
class CheckingAccount public {
. . .
void deposit(double amount)
{
transactionCount++;
balance = balance + amount;
deposit(amount); // questo è un loop!
}
. . .
}
public void deposit(double amount)
{
transactionCount++;
balance = balance + amount;
super.deposit(amount);
}
• È come invocare this.deposit(amount)
Continua…
Sintassi: chiamata via super
La classe CheckingAccount
super.methodName(parameters)
Esempio:
public void deposit(double amount)
{
transactionCount++;
super.deposit(amount);
}
public class CheckingAccount extends BankAccount
{
. . .
public void withdraw(double amount)
{
transactionCount++;
// sottrai amount da balance
Scopo:
Invocare un metodo overridden della superclasse
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()?
}
8
Risposte
Costruttori di sottoclasse
•
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.
• 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…
Costruttori di sottoclasse
Costruttori di sottoclasse
• super seguito da una parentesi indica una
chiamata al costruttore di superclasse
public class CheckingAccount extends BankAccount
{
public CheckingAccount(double initialBalance)
{
// Construct superclass
super(initialBalance);
// Initialize transaction count
transactionCount = 0;
}
. . .
}
• La chiamata a super(…) deve essere il primo
comando del costruttore 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
• NB: non confondere con la chiamata di metodo
della superclasse super.m(…)
Continua…
Sintassi
ClassName(parameters)
{
super(parameters);
. . .
}
Esempio:
public CheckingAccount(double initialBalance)
{
super(initialBalance);
transactionCount = 0;
}
Scopo:
Invocare il costruttore di superclasse.
Deve essere il primo comando del costruttore della sottoclasse.
Domanda
•
Assumendo che questa implementazione di
SavingsAccount
public class SavingsAccount extends BankAccount
{
public SavingsAccount(double rate)
{
interestRate = rate;
}
. . .
}
compili correttamente, cosa possiamo dire
riguardo i costruttori della classe BankAccount?
9
Risposta
•
Che BankAccount ha un costruttore senza
parametri
Conversioni di tipo e subtyping
• Una sottoclasse è un sottotipo della sua
superclasse
• Quindi, riferimenti di tipo sottoclasse
possono essere trattati come riferimenti
di tipo superclasse
SavingsAccount collegeFund = new SavingsAccount(10);
BankAccount anAccount = collegeFund;
Object anObject = collegeFund;
Conversioni di tipo e subtyping
• I tre riferimenti contenuti in collegeFund,
anAccount e anObject riferiscono tutti lo
stesso oggetto, di tipo SavingsAccount
Conversioni di tipo e subtyping
• Utilizzare un riferimento di tipo superclasse
causa una perdita di informazione
// anAccount : BankAccount
anAccount.deposit(1000); // OK
anAccount.addInterest(); // NO
• Assegnando un riferimento di sottoclasse ad
una variabile di superclasse:
il valore del riferimento assegnato alla variabile punta
correttamente allo stesso oggetto
Ma il tipo della variabile rivela meno informazione
Continua…
sull’oggetto
Conversioni di tipo e subtyping
• Perchè convertire, quindi?
Per riutilizzare codice scritto in termini del supertipo,
(e che potrebbe anche non conoscere il sottotipo) :
public void transfer(double amount, BankAccount other)
{
withdraw(amount);
other.deposit(amount);
}
• Questo codice può essere utilizzato per
trasferimenti su qualunque BankAccount
Conversioni di tipo e downcasting
• In alcune situazioni è utile/necessaria la
conversione inversa: da supertipo a
sottotipo
• Utilizziamo cast …
SavingsAccout anAccount = (SavingsAccount) anotherAccount.
• Questo però può essere pericoloso:
se anotherAccount non punta ad un
SavingsAccount genera un errore (eccezione)
Continua…
10
Conversioni di tipo e downcasting
Conversioni di tipo e downcasting
• Soluzione: utilizzo dell’operatore
instanceof
• Nulla di nuovo …
stesse idee e meccanismi visti quando abbiamo
parlato delle interfacce.
if (anObject instanceof BankAccount)
{
BankAccount anAccount = (BankAccount) anObject;
. . .
}
• verifica se anObject punta ad un oggetto di
tipo BankAccount (o di un sottotipo)
Domande
•
Risposte
Nel metodo transfer() qui di seguito,
che succede se modifichiamo il tipo del
secondo parametro
public void transfer(double amount, BankAccount other)
{
withdraw(amount);
other.deposit(amount);
}
•
Nel primo caso perdiamo la possibilità di
utilizzare il metodo su oggetti di tipo
BankAccount e di tipo CheckingAccount
•
Nel secondo caso abbiamo un errore perchè il
metodo deposit() non può essere invocato
su una variabile di tipo Object
da BankAccount a SavingsAccount?
da BankAccount a Object?
Polimorfismo – dynamic dispatch
Polimorfismo – dynamic dispatch
• Il tipo di una variabile non determina in modo
univoco il tipo dell’oggetto che la variabile
riferisce
• Meccanismo già discusso per le interfacce
• Ogni variabile ha due tipi
• Il metodo invocato da un messaggio è
determinato dal tipo dinamico della variabile,
non dal tipo statico
BankAccount anAccount = new CheckingAccount();
anAccount.deposit(1000);
//chiama CheckingAccount.deposit()
Statico: il tipo dichiarato
Dinamico: il tipo dell’oggetto a cui la variabile riferisce
BankAccount aBankAccount = new SavingsAccount(1000);
// tipo statico: BankAccount
// tipo dinamico: SavingsAccount
Continua…
Continua…
11
Polimorfismo – dynamic dispatch
Polimorfismo – dynamic dispatch
• Supponiamo di includere il metodo transfer
in BankAccount
• 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…
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);
Polimorfismo – dynamic dispatch
• Il compilatore verifica che esista un metodo
da selezionare in risposta al messaggio
Object anObject = new BankAccount();
anObject.deposit(1000); // Wrong!
• Meglio: verifica l’esistenza del metodo, e
decide il tipo del metodo da invocare
tra le possibili versioni overloaded
• invoca
• Il corpo del metodo, con il tipo selezionato,
viene determinato a run time.
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:
File BankAccount.java
/**
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…
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…
12
File BankAccount.java
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
File BankAccount.java
public void withdraw(double amount)
{
balance = balance - amount;
}
56:
57:
58:
59:
60:
61:
62:
63: }
/**
Saldo corrente del conto.
@return il valore del campo balance
*/
public double getBalance()
{
return balance;
}
private double balance;
/**
Trasferimento da questo conto ad un altro conto
@param amount l’importo da trasferire
@param other il conto su cui trasferire
Continua…
*/
File CheckingAccount.java
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
public void transfer(double amount, BankAccount other)
{
withdraw(amount);
other.deposit(amount);
}
/**
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
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 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 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…
13
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
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);
File AccountTester.java
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
System.out.println("Mom's savings balance = $“
+ momsSavings.getBalance());
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…
Domande
•
Supponiamo che a sia una variabile di tipo
BankAccount con valore diverso da null.
Cosa possiamo dire riguardo all’oggetto
riferito da a?
•
Se a riferisce un CheckingAccount, quale
è l’effetto del messaggio
a.transfer(1000, a)?
momsSavings.transfer(1000, harrysChecking);
harrysChecking.withdraw(400);
// test per le operazioni di fine mese
momsSavings.addInterest();
harrysChecking.deductFees();
/**
System.out.println("Harry's checking balance = $“
+ harrysChecking.getBalance());
}
Risposte
•
L’oggetto è un instanza della classe
BankAccount o di una sottoclasse.
•
Il saldo è invariato, il contatore delle
transazioni viene incrementato due volte.
(Super)classi abstract
• Nella definizione di una sottoclasse possiamo
scegliere se ridefinire o meno i metodi della
superclasse
• La interfaccia della superclasse può però
rendere la ridefinizione dei metodi necessaria
• È il caso in cui nella superclasse si vuole
fornire un metodo nell’interfaccia, senza darne
una implementazione
• Il metodo, e la superclasse sono abstract
14
(Super)classi abstract
(Super)classi abstract
• Classi abstract utili per
descrivere l’interfaccia comune di un insieme di classi
definire alcuni aspetti dell’implementazione (struttura
e metodi) lasciandone non speficicati altri
• Un costrutto intermedio tra classi e interfacce
• Esempio: potremmo ripensare alla gerarchia
radicata in BankAccount, e decidere di
strutturarla in modo che tutti i conti debbano
avere un metodo deductFees()
• Simile alla situazione dei metodi deposit()
e deposit()
• Ma per deductFees()non abbiamo una
implementazione utile per le sottoclassi
Continua…
(Super)classi abstract
(Super)classi abstract
• Due possibilità:
Definiamo un metodo con corpo vuoto
Alternativa migliore: forziamo le sottoclassi a fornire
una implementazione definendo il metodo, e la classe
abstract
public abstract class BankAccout
{
. . .
public abstract void deductFees();
. . .
}
• Una sottoclasse può sovrascrivere un
metodo abstract fornendo una
implementazione
• Una classe abstract non può essere
istanziata
Continua…
Eventi del mouse
Questo non significa che non possa avere un
costruttore
Il costruttore sarà invocato dalle sottoclassi che
forniscono implementazione ai metodi abstract
Eventi del mouse
• MouseListener vs MouseAdapter
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)
• Una classe che definisce (o eredita) un
metodo abstract deve a sua volta essere
dichiarata abstract
{/* donothing */};
• MouseAdapter è abstract e quindi non può
essere istanziata direttamente, anche se tutti
i suoi metodi sono concreti
• possiamo estenderla con un classe concreta
class MyAdapter1 extends MouseAdapter
{
/* inutile, ma compila e può essere istanziata
}
class MyAdapter2 extends MouseAdapter
{
/* gestisce solo gli eventi “click” */
}
public void MouseClicked { . . . }
Continua…
}
Continua…
15
Metodi e (sotto) classi final
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
• 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
campi, metodi e classi interne.
Continua…
Livelli di accesso raccomandati
• Variabili di istanza private.
public
Eccezioni:
• accessibile dai metodi di qualunque classe
private
public static per costanti (campi final)
• 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
Campi di classi strettamente interconnesse all’interno
di un package
• considerate se non sia più opportuno utilizzare
classi interne
Livelli di accesso raccomandati
• Metodi: public, private
protected può essere una opzione ragionevole in
questo caso
Esempio: System.out, accessibile a tutte le classi
Continua…
Object: la superclasse cosmica
• Tutte le classi definite senza una clausola extends
esplicita estendono automaticamente la classe
Object
• Classi e interfacce: public o package
Alternativa all’accesso package: classi interne
In generale, per le classi interne utilizziamo le stesse
prassi dei campi (mai public )
Esistono eccezioni: Ellipse2D.Double
• Attenzione ai default:
omettere public o private implica accesso
package
16
Object: la superclasse cosmica
•
I metodi più utili definiti da questa classe
•
String toString()
boolean equals(Object otherObject)
Object clone()
toString()
• Object.toString() restituisce una stringa
ottenuta dalla concatenazione del nome della
classe seguita dal codice hash dell’oggetto
su cui viene invocato.
Spesso overridden nelle classi di sistema e/o
nelle classi definite da utente
Continua…
toString()
toString()
• 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]"
• invocato automaticamente tutte le volte che
concateniamo un oggetto con una stringa
• Possiamo ottenere lo stesso effetto nelle
classi user-defined, ridefinendo il metodo:
public String toString()
{
return "BankAccount[balance=" + balance + "]";
}
• In questo modo abbiamo:
BankAccount momsSavings = new BankAccount(5000);
String s = momsSavings.toString();
// s = "BankAccount[balance=5000]"
Continua…
equals() – overriding
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:
• Sovrascrivendo equals() manteniamo la
firma che il metodo ha nella classe Object
public class Coin
{
. . .
public boolean equals(Object otherObject)
{
Coin other = (Coin) otherObject;
return name.equals(other.name) && value == other.value;
}
. . .
}
• Notiamo
public boolean equals(Object otherObject)
cast per recuperare informazione sul parametro
equals per confrontare campi riferimento
Continua…
Continua …
17
equals() – overriding
equals() – overriding
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 ...
• Possiamo fare meglio!
Continua …
equals() – overriding
• Dato coin:Coin,consideriamo la chiamata
coin.equals(obj)
public class Coin
overloading
{
public boolean equals(Coin other)
{
return name.equals(other.name) && value == other.value;
}
overriding
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 overloaded
}
Continua…
equals() – overriding
• Nelle sottoclassi stessa logica …
• … ma attenzione alla struttura ereditata!
public boolean equals(CollectibleCoin other)
{
if (!super.equals(other)) return false;
return year == other.year;
}
public 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;
}
. . .
private int year;
}
Continua…
Domanda
• NB: quale versione di equals nella classe
Coin invoca la chiamata
super.equals(other)?
Risposta
• La versione con il parametro di tipo Coin
other:CollectibleCoin è anche un Coin
public boolean equals(CollectibleCoin other)
{
if (!super.equals(other)) return false;
return year == other.year;
}
private int year;
}
18
Domanda
•
Risposta
Cosa dobbiamo aspettarci dalla chiamata
x.equals(x)? Deve sempre restituire
true?
Domanda
•
•
Si, a meno che, ovviamente, x non sia il
riferimento null.
Risposta
È possibile implementare equals in
termini di toString? È ragionevole
come idea?
•
Se toString restituisce una stringa che
descrive i campi dell’oggetto, questa è una
implementazione possibile
•
Invoca toString() su this e sul parametro
esplicita e confronta le due stringhe
Tuttavia, è più efficiente confrontare i
campi direttamente piuttosto che
convertirli prima in stringhe e poi
confrontarli.
clone()
clone()
• Come ben sappiamo, l’assegnamento tra due
riferimenti crea due riferimenti che puntano
allo stesso oggetto
• Talvolta è utile/necessario creare una copia
dell’intero oggetto, non del solo riferimento
BankAccount account2 = account;
Continua…
19
Object.clone()
Object.clone()
• Non ripete il cloning sistematicamente sui campi di
tipo riferimento
• Crea shallow copies (copie superficiali)
• 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)
Continua…
clone() – overriding
Overriding vs overloading
• Una classe che ridefinisce clone() deve
attenersi alla firma del metodo nella classe
Object
Object clone()
• 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
• L’uso del metodo che ne deriva è:
• Fase dinamica: dispatch
BankAccount cloned = (BankAccount) account.clone();
• Necessario il cast perchè il metodo ha
Object come tipo risultato
Polimorfismo – run time
determina il corpo del metodo, in funzione del tipo
dinamico del parametro implicito (ovvero, del
destinatario del messaggio)
Invocazione di metodi
• Dispatch articolato di quanto abbiamo visto …
• exp.m(a1,..., an)
• Metodi
dispatch dinamico, con le seguenti eccezioni:
metodi private, chiamate via super
per definire precisamente l’effetto della chiamata
dobbiamo analizzare tre aspetti:
• selezione statica
• Metodi di classe (static)
dispatch è sempre statico
• Campi
decide se è corretto invocare m(…) su exp, ovvero
se esiste un metodo da invocare
determina il tipo del metodo da invocare
• dispatch
dispatch è sempre statico, sia per variabili di istanza,
sia per variabili di classe (o static)
determina il corpo del metodo da invocare
Continua…
20
Selezione statica
Selezione Statica – Fase 1
exp.m(a1,..., an)
exp.m(a1,..., an)
• Determina il tipo statico S di exp::
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)
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
• tipo = tipo statico
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 – 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 T m(T1,...,Tn) per
l’invocazione m(a1:S1,… an:Sn), a partire da S
• Se trovi una sola firma ok, altrimenti errore
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
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
21
Best Match
Best Match – Esempi
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();
= { A.m(int) }
APP(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) {
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)}
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) }
22
Invocazione di metodi
Esempio
class A {
exp.m(a1,..., an)
•
public void m(int i) {
selezione statica: determina la firma del metodo da
invocare
•
public void m(double d){System.out.println("A.m(double)");
per definire precisamente l’effetto della chiamata dobbiamo
analizzare tre aspetti:
class B extends A {
calcolo del best match
public void m(double d){System.out.print(“B.m(double)”); }
dispatch dinamico: determina il corpo del metodo
da invocare
}
class over {
System.out.println(“A.m(int)"); }
}
se il dispatch è statico esegui il corpo del metodo determinato
dalla selezione statica
altrimenti esegui il corpo del metodo con il tipo determinato
dalla selezione statica che trovi a partire dal tipo dinamico di
exp
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
Esempio
class A {
class A {
public void m(double d){ System.out.println("A.m(double)");
public void m(int i) {
public void m(double d){ System.out.println("A.m(double)");
System.out.println(“A.m(int)"); }
public void m(int i) { System.out.println(“A.m(int)"); }
}
}
class B extends A {
class B extends A {
public void m(double d){System.out.print(“B.m(double)”); }
}
public void m(double d){System.out.print(“B.m(double)”); }
}
class over {
class over {
public static void main(String[] args) {
{ A a = new B();
}
public static void main(String[] args) {
a.m(1); }
{ B b = new B();
// Selezione statica: BEST(A, m(int)) = { A.m(int) }
// Dispatch: B non ridefinisce m(int). Quindi esegui A.m(int)
Metodi private : dispatch statico
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
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()”
Continua…
23
Chiamate via super: dispatch statico
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); }
}
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()”
. . .
D d = new D();
d.m();
d.n();
System.out.println(d.str); // D
C c = d;
c.m();
// C
((D)c).n();
// D
// C
// D
System.out.println(c.str); // C
24
Scarica

Ereditarietà