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