Modulo 4: Ereditarietà, interfacce e clonazione
Argomenti Trattati: Classi, Superclassi e Sottoclassi Ereditarietà Ereditarietà ed Attributi Privati Override super Ereditarietà e Costruttori Polimorfismo Evitare l'ereditarietà Il Casting
Modulo 3: Ereditarietà, interfacce e clonazione Classi, Superclassi e Sottoclassi
L'ereditarietà L'idea che sta alla base del concetto di ereditarietà è che è possibile creare nuove classi a partire da classi esistenti. Quando si eredita da una classe, si riutilizzano (ereditano) i suoi metodi e campi ed è possibile aggiungerne dei nuovi.
L'ereditarietà ESEMPIO: Consideriamo una classe Impiegato che rappresenta tutti i tipi di lavoratori di un'azienda. Vogliamo modellare anche una classe Manager che, continua ad essere un impiegato, ma ha delle caratteristiche in più: hanno uno stipendio come un impiegato generico ma ottengono dei bonus quando raggiungono i risultati previsti. Possiamo quindi creare una classe Manager che estende la classe Impiegato, ereditandone le caratteristiche e aggiungendogliene altre.
L'ereditarietà SINTASSI: Per indicare che una classe ne estende un'altra si utilizza la parola chiave extends public class Figlio extends Genitore
L'ereditarietà ESEMPIO: Considerando l'esempio della Manager che estende la classe Impiegato: public class Manager extends Impiegato
L'ereditarietà La parola extends indica che si definisce una nuova classe che deriva da una classe esistente. La classe esistente prende il nome di superclasse, mentre la nuova classe quello di sottoclasse. Attenzione: con il termine superclasse non si intende che la classe è superiore ed ha funzionalità in più; ma solo che è superiore nella gerarchia ed anzi ha funzionalità in meno, in quanto i suoi figli ereditano le sue caratteristiche e ne aggiungono altre.
L'ereditarietà: gli attributi privati Ricordiamo che le sottoclassi NON EREDITANO gli attributi ed i metodi definiti private. Se si vuole far ereditare qualcosa, va dichiarata public o protected. ATTENZIONE: va fatta chiarezza sul concetto di non ereditano, con esso si intende che la classe Figlia continua a possedere eventuali attributi privati ereditati dal Padre, ma i suoi metodi interni non possono accedere a tali campi.
L'ereditarietà: gli attributi privati Chiariamo questo concetto ritornando all'esempio di Impiegato e Manager. Consideriamo per il momento che l'impiegato ha solo due attributi privati (nome, cognome) con i relativi getter e setter pubblici. Consideriamo poi la classe Manager che per il momento estende la classe impiegato ma non aggiunge nulla.
L'ereditarietà: gli attributi privati Classe Impiegato: public class Impiegato private String nome; private String cognome; public setnome(string n) nome=n; Classe Manager: public class Manager extends Impiegato //per il momento non aggiunge niente public String getnome( ) return nome; //idem per cognome I METODI DELLA CLASSE IMPIEGATO NON POTRANNO ACCEDERE AGLI ATTRIBUTI nome E cognome, MA DI FATTO UN OGGETTO IMPIEGATO LI POSSIEDE E POTRÒ PER ESEMPIO CHIAMARE I getter E setter PER UTILIZZARLI
L'ereditarietà: gli attributi privati Utilizzo un oggetto di tipo Manager: Manager m = new Manager ( ); m.setnome( ciccio ); String n = m.getnome( ); L'OGGETTO DI TIPO MANAGER CONTINUA A POSSEDERE GLI ATTRIBUTI nome E cognome ANCHE SE ERANO DICHIARATI privati ALL'INTERNO DELLA CLASSE IMPIEGATO.
L'ereditarietà: gli attributi privati Metodi della classe Manager (VERSIONE ERRATA): public class Manager extends Impiegato public String iniziali( ) String i_n = nome.substring(0,1); String i_c = cognome.substring(0,1); String i = i_n +. + i_c +. ; return i; ERRORE: I METODI DELLA CLASSE MANAGER NON POSSONO ACCEDERE AGLI ATTRIBUTI PRIVATI nome E cognome DELLA CLASSE IMPIEGATO, NONOSTANTE DI FATTO UN OGGETTO DI TIPO MANAGER AL SUO INTERNO POSSIEDE TALI ATTRIBUTI (nel nostro esempio accessibili tramite i getter e setter pubblici ereditati)
L'ereditarietà: gli attributi privati Metodi della classe Manager (VERSIONE CORRETTA): public class Manager extends Impiegato public String iniziali( ) String i_n = getnome( ).substring(0,1); String i_c = getcognome( ).substring(0,1); String i = i_n +. + i_c +. ; return i; I METODI LA CLASSE Manager NON POSSONO ACCEDERE AGLI ATTRIBUTI nome E cognome PERCHÈ SONO PRIVATI. UN OGGETTO DI TIPO Manager PERÒ POSSIEDE TALI ATTRIBUTI INFATTI POSSO ACCEDERVI TRAMITE I getter
L'ereditarietà: Override Può capitare che alcuni metodi pubblici della superclasse non sono adatti alla sottoclasse ed è quindi necessario riscriverli. In tal caso si parla di override, ovvero nella classe figlio scrivo un metodo con la stessa signature (nome, numero e tipo di parametri) ma che fa cose diverse.
L'ereditarietà: Override Ritornando all'esempio possiamo pensare che la classe Impiegato ha un metodo ha un attributo privato salario ed i relativi metodi pubblici getsalario( ) e setsalario ( ) Voglio che la classe Manager faccia l'override del metodo getsalario ( ) scrivendo un metodo che non ritorna semplicemente l'attributo salario ma il salario + bonus.
L'ereditarietà: Override Classe Impiegato: public class Impiegato private int salario; public int getsalario( ) return salario; Classe Manager: public class Manager extends Impiegato private int bonus; public int getsalario( ) int r = salario + bonus; return r; DEFINISCO UN NUOVO METODO getsalario CHE NEGLI OGGETTI DI TIPO MANAGER SOVRASCRIVE QUELLO DI IMPIEGATO.
L'ereditarietà: Override Classe Impiegato: public class Impiegato private int salario; public int getsalario( ) return salario; Classe Manager: public class Manager extends Impiegato private int bonus; public int getsalario( ) int r = salario + bonus; return r; ATTENZIONE: C'È UN ERRORE! COME DETTO PRECEDENTEMENTE, I METODI DI MANAGER NON POSSONO ACCEDERE AGLI ATTRIBUTI PRIVATI DI IMPEGATO!
L'ereditarietà: Override Classe Impiegato: public class Impiegato private int salario; public int getsalario( ) return salario; Classe Manager: public class Manager extends Impiegato private int bonus; public int getsalario( ) int r = getsalario( ) + bonus; return r; ATTENZIONE: È ANCORE ERRORE! IN QUESTO MODO CHIAMEREI IL NUOVO METODO getsalario( ) CHE STO DEFINENDO ORA NELLA CLASSE MANAGER ENTRANDO IN CICLO INFINITO
L'ereditarietà: parola chiave super Classe Manager: public class Manager extends Impiegato private int bonus; public int getsalario( ) int r = super.getsalario( ) + bonus; return r; INTRODUCO LA PAROLA CHIAVE super CHE MI PERMETTE DI INDICARE CHE VOGLIO FARE RIFERIMENTO AL METODO getsalario ( ) DELLA SUPERCLASSE Impiegato E NON A QUELLO DELLA CLASSE Manager
L'ereditarietà: Nota Abbiamo visto che, con l'ereditarietà, una sottoclasse può aggiungere campi e metodi o effettuare l'ovverride di metodi della superclasse Tuttavia, traminte l'ereditarietà NON SI POSSONO ELIMINARE né metodi né campi della superclasse.
L'ereditarietà: Costruttori Predefiniti Come si comporta il meccanismo dei costruttori nell'ereditarietà? Se non definisco costruttori personalizzati oppure ho solo costruttori predefiniti (senza parametri), non ho particolari problemi: - nella superclasse viene chiamato il costruttore predefinito che inizializza gli attributi al valore di default; - nella sottoclasse viene chiamato il costruttore predefinito della superclasse per inizializzare gli attributi della superclasse (quelli private) al valore di default; ed il costruttore predefinito della sottoclasse per inizializzare gli attributi della sottoclasse (che non possiede la superclasse) al valore di default.
L'ereditarietà: Costruttori Personalizzati Se ho dei costruttori personalizzati, la situazione si complica: - consideriamo che nella superclasse ho definito un costruttore personalizzato, quindi non esiste più il costruttore predefinito (senza parametri) che Java mette di default. - nella sottoclasse il costruttore andrebbe a cercare il costruttore predefinito della superclasse; questo però non esiste e Java mi darebbe errore.
L'ereditarietà: Costruttori Personalizzati ESEMPIO: Ritornando all'esempio consideriamo che Impiegato abbia un costruttore personalizzato: public class Impiegato public Impiegato(String n, String c, int s ) nome = n; cognome = c; salario = s;
L'ereditarietà: Costruttori Personalizzati Come agire: - devo indicare nella sottoclasse che voglio utilizzare un determinato costruttore della superclasse - per far ciò si utilizza anche in questo caso la parola chiave super - in questo caso è però utilizzata con uno scopo diverso: chiamare il costruttore della superclasse all'interno della sottoclasse.
L'ereditarietà: Costruttori Personalizzati ESEMPIO: La classe Manager dovrà avere un costruttore che utilizza il costruttore personalizzato di Impiegato perchè quello predefinito non esiste più: public class Manager extends Impiegato public Manager (String n, String c, int s ) super (n, c, s); bonus = 0; VA A CHIAMARE IL COSTRUTTORE: Impiegato (String n, String c, int s) DELLA CLASSE IMPIEGATO
L'ereditarietà: Costruttori Personalizzati Attenzione: in questi casi la chiamata al costruttre della superclasse, con la parola chiave super, deve essere la PRIMA ISTRUZIONE del costruttore della sottoclasse.
L'ereditarietà: Gerarchie L'ereditarietà non si limita a un solo livello di classi. È possibile avere sottoclassi di sottoclassi e di fatto formare una gerarchia. ATTENZIONE: non è permessa però l'ereditarietà Multipla, ovvero una classe non può estendere 2 (o più) classi, ma solo 1. Ovviamente invece è possibile il contrario, ovvero una classe può essere estesa da più classi.
L'ereditarietà: Polimorfismo Quando abbiamo introdotto l'ereditarietà abbiamo detto che è possibile utilizzare un oggetto della sottoclasse ogni qualvolta è richiesto quello della superclasse. Impiegato i = new Impiegato ( ); Impiegato m = new Manager ( ); ASSEGNO UN OGGETTO DI TIPO MANAGER AD UNA VARIABILE DI TIPO IMPIEGATO m SARÀ UN OGGETTO DI TIPO MANAGER MA UTILIZZABILE SOLO COME IMPIEGATO
L'ereditarietà: Polimorfismo Quando accade una cosa del genere, si parla di polimorfismo. Ovvero una variabile che ha come tipo una determinata classe può far riferimento sia ad oggetti di questa classe che ad oggetti delle sue sottoclassi. Con questa tecnica si applica il principio di sostituzione: si può utilizzare un oggetto della sottoclasse ogni volta che il programma si aspetta un oggetto della superclasse.
L'ereditarietà: Polimorfismo (dettagli) E' lecito chiedersi: l'oggetto m, sarà un Manager o un Impiegato? Impiegato i = new Impiegato ( ); Impiegato m = new Manager ( ); E' una via di mezzo: - è di fatto un oggetto di tipo Manager; - posso utilizzare però solo i metodi che ha anche Impiegato;
L'ereditarietà: Polimorfismo (dettagli) Classe Impiegato: public class Impiegato public String ruolo( ) return Imp ; Classe Manager: public class Manager extends Impiegato public String ruolo( ) return Man ; Ho un metodo ruolo( ) che mi ritorna una Stringa contenente il ruolo nell'azienda: la classe Impiegato ritorna Imp ; la classe Manager fa l'ovverride di tale metodo e ritorna Man.
L'ereditarietà: Polimorfismo Cosa accade in questo caso? Impiegato m = new Manager ( ); System.out.println( m.ruolo( ) ); VERRÀ STAMPATO Imp OPPURE Man? In questo caso, essendo m un oggetto di tipo Manager, anche se vi accedo con una variabile di tipo Impiegato, viene preso il metodo ruolo ( ) della classe Manager e non quello della classe Impiegato.
L'ereditarietà: Polimorfismo (dettagli) Classe Impiegato: public class Impiegato Classe Manager: public class Manager extends Impiegato private int bonus; public int getbonus( ) return bonus; La classe Manager aggiunge un attributo bonus ed un metodo getbonus( ) per accedervi.
L'ereditarietà: Polimorfismo Cosa accade in questo caso? Impiegato m = new Manager ( ); System.out.println( m.getbonus( ) ); ERRORE: ANCHE SE È UN OGGETTO DI TIPO EFFETTIVO MANAGER, POSSO UTILIZZARE SOLO I METODI PRESENTI IN IMPIEGATO In questo caso, è vero che m è un oggetto di tipo Manager ma, accedendovi con una variabile di tipo Impiegato, posso utilizzare solo i metodi che ha Impiegato perchè appunto il sistema si aspettava un oggetto di tipo Impiegato.
L'ereditarietà: Evitare l'ereditarietà A volte si vuole evitare che una classe possa essere estesa; si vuole fare in modo cioè che non possa avere sottoclassi. Se si vuole far ciò bisogna dichiarare una classe final; la stessa parola chiave che abbiamo appunto utilizzato per dichiarare le costanti. final class NomeClasse
L'ereditarietà: Evitare l'ereditarietà È possibile dichiarare final anche un metodo; in questo caso significa che nessuna sottoclasse può effettuare l'override class NomeClasse final void nomemetodo( )
L'ereditarietà: il Casting Abbiamo già visto che la conversione di un tipo in un altro prende il nome di casting. In precedenza abbiamo visto solo il casting di tipi primitivi. double x = 3.405; int nx = (int) x; //x vale 3 Vediamo come comportarci con gli Oggetti.
L'ereditarietà: il Casting Abbiamo visto che il Polimorfismo ci permette di fare cose del genere: Impiegato i = new Manager( ); ASSEGNO AD UNA VARIABILE DEL TIPO DELLA SUPERCLASSE UN OGGETTO DELLA SOTTOCLASSE ANCHE SE i È UN OGGETTO DI TIPO MANAGER, POSSO UTILIZZARE SOLO I METODI CHE HA LA CLASSE IMPIEGATO
L'ereditarietà: il Casting Se voglio utilizzare metodi propri di Manager (ovvero che non sono stati ereditati da Impiegato) devo fare un casting esplicito della variabile di tipo Impiegato in tipo Manager: Impiegato i = new Manager( ); Manager m = (Manager) i ; ORA m (CHE CONTINUA AD ESSERE LO STESSO OGGETTO DI i) È A TUTTI GLI EFFETTI UN MANAGER E POSSO UTILIZZARLO COME TALE.