• 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