Ereditarietà L'ereditarietà consente allo sviluppatore di riutilizzare il codice già scritto creando sottoclassi a partire da classi già definite. La sottoclasse eredita tutti gli attributi e tutti i metodi definiti dalla superclasse, senza doverli riscrivere, ad eccezione di quelli che sono stati dichiarati private. La classe figlia può eventualmente aggiungere i propri attributi e i propri metodi. Sono quindi ereditati tutti i membri con visibilità public indipendentemente dal package in cui si trovano le due classi e tutti i membri con visibilità package (cioè se sottoclasse e superclasse sono contenute nello stesso package). Se invece sottoclasse e superclasse appartengono a package (o sottopackage) differenti, allora la visibilità dei membri della superclasse deve essere protected se vogliamo garantire l'ereditarietà. La relazione tra la classe generale e la classe più specializzata prende il nome di relazione ISA (è un). Per esempio, la classe Studente può estendere la classe Persona. Infatti ogni studente è una persona. In Java è presente un solo tipo di ereditarietà, detta ereditarietà singola, cioè ogni classe può avere al più una superclasse, mentre la superclasse può anche avere molte sottoclassi. Non è consentita, almeno in maniera diretta, la cosiddetta ereditarietà multipla. Vedremo comunque che in Java è consentita una certa forma di ereditarietà multipla mediante l'utilizzo delle interfacce (rimandiamo il lettore alla lettura dell'articolo sulle interfacce). Infatti vedremo che una classe può derivare da una sola superclasse ma può implementare una o più interfacce. ereditarieta3.pdf Pag. 1/7 Cozzetto
Consideriamo un semplice esempio. Partiamo dalla classe Rettangolo. Un rettangolo ha come attributi la base (un valore double) e l'altezza (un altro valore double). Per semplicità supponiamo che la visibilità dei due attributi sia di tipo package. Metodi utili sono area() e perimetro() (oltre a tostring() che è comune a tutti gli oggetti Java). Consideriamo ora un Quadrato. É evidente che un quadrato è una specializzazione di un rettangolo, come appare dal seguente diagramma. Si dice che la classe Quadrato deriva dalla classe Rettangolo e si scrive: public class Rettangolo { double base; double altezza; // fine classe Rettangolo public class Quadrato extends Rettangolo { // fine classe Quadrato Ricordiamo inoltre che ogni classe deriva dalla classe speciale Object (che rappresenta la radice di tutte le classi di Java). Super La variabile speciale super è un riferimento a un'istanza della superclasse e permette di richiamare metodi (non statici) e attributi (con visibilità public, package o protected) della superclasse secondo lo schema super.metodo() e super.attributo. Il costruttore senza argomenti in una sottoclasse si può richiamare mediante la sintassi super(), mentre il costruttore con ereditarieta3.pdf Pag. 2/7 Cozzetto
argomenti si richiama con la sintassi super(lista-argomenti). Override In una sottoclasse si possono aggiungere nuovi metodi (o nuovi attributi) non appartenenti alla superclasse (per esempio il metodo getlato() della figura precedente), come già detto, o ridefinire (o sovrascrivere) metodi con la stessa segnatura e stesso tipo di ritorno della funzione (cioè con lo stesso prototipo). La segnatura di un metodo è costituita dal nome del metodo e dai tipi dei suoi argomenti (il tipo di ritorno non appartiene alla segnatura). Per definizione quindi prototipo di un metodo = tipo di ritorno +segnatura. Per indicare che un metodo è un override di un metodo della sua superclasse, di solito si usa scrivere, prima del nome del metodo, l'annotazione. In questo modo il compilatore può in effetti verificare che si tratta veramente di un override e segnalare eventuali errori. Il meccanismo dell'override è la base di un importante concetto della programmazione ad oggetti, il polimorfismo, sul quale torneremo. Il polimorfismo permette di avere lo stesso metodo definito in classi diverse e di usare una stessa istruzione per richiamare il metodo opportuno in base all'oggetto che si sta utilizzando. Consideriamo il diagramma UML seguente. La classe Gatto discende dalla classe Animale nella quale è definito il metodo emettiverso() (che potrebbe essere anche un metodo astratto, cioè non implementato). Anche la classe Cane discende dalla classe Animale e anch'essa ridefinisce il metodo emettiverso(). Il richiamo del metodo emettiverso() su due oggetti differenti permette infatti di avere comportamenti differenziati (il metodo cioè assume più forme). public class Animale { String nome; public Animale() { ereditarieta3.pdf Pag. 3/7 Cozzetto
// emettiverso() potrebbe anche essere un metodo astratto // cioè non implementato // le classi figlie hanno quindi l'obbligo di implementarlo public emettiverso() { public void mostra() { // fine classe Animale public class Cane extends Animale { public void emettiverso() { System.out.println( Bau bau bau ); // Overloading public void mostra(string msg) { // fine Cane public class Gatto extends Animale { public void emettiverso() { System.out.println( Miao miao miao ); public void mostra() { // fine classe Gatto public class Main { public static void main(string[] args) { Cane c = new Cane( Brick, Canis ); Gatto g = new Gatto( Garfield, Felis ); c.emettiverso(); // Bau bau bau g.emettiverso(); // Miao miao miao // fine classe Main ereditarieta3.pdf Pag. 4/7 Cozzetto
Overloading All interno di una classe è possibile definire più volte (sovraccaricare, dall'inglese to overload) un metodo, in modo da adeguarlo a contesti di utilizzo differenti e in modo da evitare l'uso di troppi nomi per le funzioni. Due metodi con lo stesso nome possono coesistere in una stessa classe (o in classi differenti) se restituiscono lo stesso tipo e hanno segnature differenti. Questo è il motivo per cui è possibile avere in una (stessa) classe costruttori differenti. Nella classe Cane inoltre vi è un sovraccarico (overloading) del metodo mostra() che visualizza i dati di un cane, aggiungendo al messaggio in output una stringa di comodo msg. Invece nella classe Gatto, vi è la sovrascrittura (override) del metodo mostra() (la segnatura e il tipo di ritorno sono gli stessi della classe padre). Un altro esempio di overloading è il metodo println. Questo metodo consente di stampare su video sia stringhe che numeri interi o reali. Esistono quindi diversi metodi println con lo stesso nome che vengono richiamati allo stesso modo ma con parametri diversi, come nei seguenti esempi. System.out.println( Stringa testuale ); System.out.println(43.53); System.out.println(16); L'overloading fornisce un modo per far assumere allo stesso metodo dei comportamenti classificabili come polimorfismo: a seconda dei parametri che vengono passati viene eseguito un comportamento diverso. In Java, non c'è nessuna annotazione per indicare l'overloading. Rappresentazione UML degli oggetti derivati Ricordiamo che un oggetto di tipo Quadrato è anche un oggetto di tipo Rettangolo, per cui è possibile scrivere in java Rettangolo q = new Quadrato(3); ma non è vero il contrario. /* * Rettangolo.java Un esempio completo package it.mauriziocozzetto.ereditarieta; * * @author maurizio public class Rettangolo { double base; double altezza; ereditarieta3.pdf Pag. 5/7 Cozzetto
public Rettangolo() { base = 2; altezza = 1; // Overloading del costruttore precedente public Rettangolo(double b, double h) { base = b; altezza = h; public double area() { return base * altezza; public double perimetro() { return 2*base + 2*altezza; public String tostring() { return "Il rettangolo ha base lunga "+base+", altezza "+altezza+", area "+area()+" e perimetro "+perimetro(); // fine classe Rettangolo /* * Quadrato.java package it.mauriziocozzetto.ereditarieta; * * @author maurizio public class Quadrato extends Rettangolo { public Quadrato() { // la classe Quadrato eredita il costruttore di Rettangolo // mediante la parola riservata super super(1,1); // Overloading del costruttore public Quadrato(double lato) { super(lato,lato); // sovrascrittura del metodo della superclasse; // l'attributo base viene ereditato dalla classe Rettangolo, come pure i metodi area() e perimetro() // la parola riservata super è un riferimento alla superclasse ereditarieta3.pdf Pag. 6/7 Cozzetto
public String tostring() { return "Il quadrato è di lato "+this.getlato()+", area "+super.area()+", perimetro "+super.perimetro(); // metodo aggiunto per ottenere il lato del quadrato public double getlato() { return super.base; // oppure super.altezza; // fine classe Quadrato /* * Main.java package it.mauriziocozzetto.ereditarieta; * * @author maurizio public class Main { * @param args the command line arguments public static void main(string[] args) { // creo un quadrato di lato 3 Quadrato q = new Quadrato(3); // l'attributo base (e anche altezza) viene ereditato da Rettangolo System.out.println("Lato "+q.base); // oppure q.altezza oppure q.getlato(); // anche i metodi area() e perimetro() della classe rettangolo vengono ereditati System.out.println("Area "+q.area()); System.out.println("Perimetro "+q.perimetro()); // viene eseguito il metodo tostring() della classe Quadrato (tostring() sovrascrive il // metodo tostring() della classe Rettangolo) System.out.println(q.toString()); // fine Main ereditarieta3.pdf Pag. 7/7 Cozzetto