Oggetti, classi, ereditarieta`
Corso di Programmazione ed Algoritmi Master di BioInformatica
Matteo Baldoni
Dipartimento di Informatica
Universita` degli Studi di Torino C.so Svizzera, 185 I-10149 Torino baldoni@di.unito.it
Chi sono? Come contattarmi?
Matteo Baldoni
Dipartimento di Informatica Università degli Studi di Torino Corso Svizzera, 185
I-10149 Torino (ITALY) e-mail: baldoni@di.unito.it
URL: http://www.di.unito.it/~baldoni Tel.: +39 011 670 67 56
Fax.: +39 011 75 16 03
“Sono nato a Torino nel 1968, nel febbraio 1993 mi sono laureato con lode in Scienze dell’Informazione e nel febbraio 1998 sono diventato Dottore in Ricerca in Informatica.
Dal luglio 1999 sono ricercatore in Informatica presso il Dipartimento di
Ogni oggetto ha un’interfaccia
e ogni oggetto ha un
tipo
Oggetti
Un oggetto rappresenta un dato, ed è costituito da
■ stato: collezione di variabili
■ comportamento: collezione di operazioni (metodi)
Esempio - contatore STATO
int c;
METODI
void iniz(int i) inizializza il contatore a i void incr() incrementa il contatore di 1 void decr() decrementa il contatore di 1
int val() restituisce il valore del
Incapsulamento
■ I dati e le procedure che li manipolano sono raggruppati in un’unica entità, l’oggetto.
■ Il mondo esterno ha accesso ai dati solo tramite un insieme di operazioni (metodi) che costituiscono
l’interfaccia dell’oggetto. I dettagli dell’implemen-tazione sono nascosti (INFORMATION HIDING)
Contatore
void iniz(int i) void incr()
void decr() int val()
INTERFACCIA IMPLEMENTAZIONE
CONTATORE
int c;
void iniz(int i) {c=1;}
void incr() {++c;}
void decr() {--c;}
int val() {return c;}
Invio di Messaggi
■ Un programma è costituito da un insieme di oggetti.
■ Gli oggetti sono dinamici - creati e distrutti durante l'esecuzione del programma.
■ Un oggetto A, per agire su un altro oggetto B, invia un messaggio a B chiedendogli di eseguire uno dei metodi della sua interfaccia.
■ Lo scambio di messaggi è l'unico modo di comunicare tra oggetti
Es.: esegui il metodo m1
Invio di Messaggi
finestra
0 esegui incr()
c
bottone 1
contatore invia valore di c
display
bottone 3
Invio di Messaggi
■ invio di messaggio = invocazione di un metodo
■ In Java, e altri linguaggi, si usa la notazione con il punto:
oggetto.metodo(…)
3
10 cont1
cont2
int x,y;
cont1.incr();
cont2.decr();
x = cont1.val();
y = cont2.val();
x ha valore 4 e y ha valore 9
Invio di Messaggi
■ In un linguaggio tradizionale, per eseguire una operazione su un dato, dovremmo passare il dato come parametro:
◆ incr(cont1) o iniz(cont1, 3)
■ Nella programmazione ad oggetti, invece, l'oggetto su cui si esegue una operazione non viene passato come
parametro, perché figura già come destinatario del
messaggio (può essere considerato come un parametro implicito)
◆ cont1.incr() o cont1.iniz(3)
Classi
■ I più diffusi linguaggi ad oggetti sono basati sul concetto di
classe
come insieme di oggetti con struttura e comportamento simili■ La classe definisce un tipo
■ Istanza di una classe = oggetto della classe
Istanze
3 10
cont1 cont2
Classi (Java)
■ Una classe realizza l'implementazione di un tipo di dato astratto.
■ In Java:
class Contatore { int c;
void iniz(int i) {c = i;}
void incr() {++c;}
void decr() {--c;}
int val() {return c;}
Classi (Java)
■ Per realizzare information hiding si usano le parole riservate
◆ public - interfaccia
◆ private - implementazione
■ Per il momento trascuriamo questo aspetto
class Contatore {
private int c;
public void iniz(int i) {c = i;}
public void incr() {++c;}
public void decr() {--c;}
public int val() {return c;}
Variabili private (Java)
■ Si noti che, se la variabile c fosse public, il metodo val sarebbe inutile: per conoscere il valore di un contatore cont basterebbe usare cont.c
■ Tuttavia è buna norma di programmazione impedire
l’accesso diretto alle variabili di un oggetto, dichiarandole
class Contatore {
private int c;
public void iniz(int i) {c = i;}
public void incr() {++c;}
public void decr() {--c;}
public int val() {return c;}
}
Tipi
■ Java è un linguaggio tipato: tutte le variabili hanno un tipo.
■ Il tipo di una variabile deve sempre essere dichiarato e può essere:
◆ una classe
◆ un tipo primitivo
Contatore cont;
int x,y;
char ch;
Tipi di dato primitivi (Java)
■ Java fornisce diversi tipi semplici primitivi.
■ A differenza di altri linguaggi, la dimensione dei tipi numerici è fissata per consentire la portabilità dei programmi:
◆ byte 8 bit
◆ short 16 bit
◆ int 32 bit
◆ long 64 bit
◆ float 32 bit
◆ double 64 bit
◆ char 16 bit (Unicode)
Come si crea un’istanza
■ new Contatore()
■ Crea un nuovo oggetto di tipo Contatore e ne restituisce il puntatore (handle)
■ Tutti gli oggetti sono allocati dinamicamente (quando si esegue la new) e sono manipolati attraverso una handle (puntatore) (assegnamento, passaggio di parametri)
Gestione della memoria
■ Nella maggior parte dei linguaggi di programmazione le attivazioni di procedure sono gestite con uno stack (pila)
■ Quando una procedura è chiamata, si inserisce un record di attivazione in cima alla pila, e lo si toglie quando
l'esecuzione della procedura termina
■ Un record di attivazione contiene le informazioni sul controllo (indirizzo di ritorno) ed i dati locali
■ Possibilità di chiamate ricorsive di procedure
Gestione della memoria
Codice
STATICA dati globali
STACK
record di attivaz.
dati locali
HEAP dati dinamici
Recupero della memoria
■ Garbage collection (Scheme, Prolog, Java)
■ Il programmatore può solo allocare dati dinamici
■ Una procedura di sistema, il garbage collector, si
preoccupa di recuperare tutte le aree di memoria nello heap non più raggiungibili in modo da poterle riutilizzare
■ Altri linguaggi come Pascal o C lasciano al programmatore la responsabilità di recuperare la memoria, con possibilità di commettere errori
Allocazione dinamica
Contatore cont1, cont2; cont1 cont2
Allocazione dinamica
Contatore cont1, cont2;
cont1 = new Contatore();
cont2 = new Contatore();
cont1.iniz(3);
cont2.iniz(10);
cont1 cont2
3
10
HEAP
Allocazione dinamica
Contatore cont1, cont2;
cont1 = new Contatore();
cont2 = new Contatore();
cont1.iniz(3);
cont2.iniz(10);
cont2 = cont1;
cont1 cont2
3
10
HEAP
Questo oggetto non è più accessibile.
Può essere recuperato dal garbage collector.
Puntatori (Java)
■ In Java non esistono puntatori espliciti Contatore cont;
■ Fino a quando non si esegue una new, la variabile cont non è associata a nessun oggetto
■ Viceversa in C++ si può dichiarare
sia Contatore cont1; sia Contatore *cont2;
■ cont1 è associato ad un contatore creato "staticamente”, cont2 è associato ad un contatore creato dinamicamente (vedi Java)
Come si inizializza un oggetto
■ Si può chiamare esplicitamente un metodo di inizializzazione:
Contatore cont1;
cont1 = new Contatore();
cont1.iniz(3);
■ L'inizializzazione di un oggetto è una operazione molto importante.
■ Java (e C++) forniscono la nozione di costruttore, che consente di inizializzare automaticamente un oggetto al momento della creazione.
Costruttori
■ L'oggetto viene inizializzato al momento della creazione, invocando automaticamente il costruttore.
■ L’istruzione Contatore cont1 = new Contatore(3); crea un contatore e lo inizializza a 3.
class Contatore { int c;
Contatore(int i) {c = i;}
void incr() {++c;}
void decr() {--c;}
int val() {return c;}
}
costruttore
Overloading di metodi (e costruttori)
■ Overloading: metodi diversi possono avere lo stesso nome.
■ I metodi "overloaded" si distinguono uno dall'altro in base alla lista dei tipi degli argomenti.
class Contatore { int c;
Contatore() {c = 0;}
Contatore(int i) {c = i;}
void incr() {++c;}
void incr(int n) {c += n;}
void decr() {--c;}
int val() {return c;}
Il costruttore di “default” in Java
■ Se una classe non ha alcun
costruttore è presente un costruttore di “default” NomeDellaClasse() (che non esegue nessuna operazione particolare)
■ Se una classe ha invece un
qualsiasi costruttore il costruttore di
“default” non viene piu` aggiunto e quindi se lo si desidera deve essere esplicitamente aggiunto.
class Contatore { int c;
ContatoreContatore() () {}{}
void incr() {++c;}
void incr(int n) {c += n;}
void decr() {--c;}
int val() {return c;}
}
class Contatore { int c;
Contatore() {}
Contatore(int i) {c = i;}
void incr() {++c;}
void incr(int n) {c += n;}
I tipi semplici non sono oggetti (Java)
■ I tipi semplici, come ad es. gli interi, non vengono allocati dinamicamente nello heap, a differenza di altri linguaggi "puri"
come Smalltalk
■ Esistono classi wrapper che trasformano tipi semplici in oggetti
Contatore cont;
cont = new Contatore(3);
int x = 10;
cont x
3
HEAP
THIS
■ Come può un oggetto mandare un messaggio a se stesso, ossia invocare un proprio metodo?
■ Con la parola chiave this
class A { ...
void p() {...}
void m() { ... this.p(); ...}
}
si può anche scrivere solo p()
In alcuni casi this è indispensabile
THIS come costruttore
■ this da solo indica un costruttore della stessa classe
class Contatore { int c;
Contatore(int n) {c=n;}
Contatore() {this(10);}
void incr() {++c;}
void decr() {--c;}
int val() {return c;}
}
chiama il costruttore con un argomento
(inizializza il contatore a 10)
Variabili e metodi di classe
■ E' possibile definire variabili (o metodi) associate ad una classe, condivise da tutte le istanze di quella classe
■ In Java sono individuate dalla parola chiave static
class Conta {
static int numContatori = 0;
int c;
Conta(int i) {c = i;
++numContatori;}
void incr() {++c;}
void decr() {--c;}
Variabili e metodi di classe
■ Le variabili di classe sono visibili da tutte le istanze della classe.
■ Sono variabili globali (per tutte le istanze di una classe).
2
classe Conta
numContatori
8 c 5
c cont1
cont2
Variabili e metodi di classe
■ Una classe può essere considerata come un oggetto (di tipo Class) che viene allocato staticamente (all'inizio dell'esecuzione del programma)
■ Le variabili ed i metodi di classe (static) sono accessibili attraverso il nome della classe
int x,y;
Conta cont = new Conta(3);
cont.incr();
....
x = cont.c;
y = Conta.numContatori;
Programmi in Java
■ Un programma in Java è una collezione di classi
■ Una di queste deve contenere un metodo main
■ L'esecuzione inizia dal main
■ Il metodo main deve essere statico, perché altrimenti bisognerebbe creare un oggetto della classe Esempio prima di poterlo eseguire
class Esempio {
public static void main(String arg[]) {
System.out.println("questo è un esempio");
}
Compilazione di un programma in Java
■ Si crea un file <nome_file>.java contenente una o più classi C1, C2, … e lo si compila.
> javac <nome_file>.java
■ Il compilatore crea un file .class in codice intermedio (bytecode) per ogni classe contenuta nel file .java:
C1.class, C2.class, ...
Esecuzione di un programma in Java
■ Si chiama l'interprete su una classe che contiene il main (Es. C1)
> java C1
■ L'interprete alloca questa classe e comincia ad eseguire il main.
Un programma completo
class Contatore { int c;
Contatore(int n) {c=n;}
void incr() {++c;}
void decr() {--c;}
int val() {return c;}
}
class UsaCont {
public static void main(String arg[]) { Contatore cont = new Contatore(5);
cont.incr();
System.out.println("valore =" + cont.val());
} }
> javac <nome_file>.java
> java UsaCont
Compilazione in Java
■ Una unità di compilazione è un file .java che contiene delle definizioni di classi.
■ Al massimo una di queste classi può essere public: in questo caso deve avere lo stesso nome del file (senza .java).
■ Il compilatore produce un file .class per ogni classe nel file.
■ L'interprete ha la responsabilità di caricare e interpretare questi file.
Riuso del software:
Ereditarieta`, Composizione, Polimorfismo e
Binding Dinamico
Ereditarietà
■ Meccanismo per lo sviluppo incrementale di programmi
■ Consente di estendere classi preesistenti aggiungendo o modificando componenti (variabili o metodi)
Ereditarietà
■ Data la classe di sopra vogliamo estendere la finestra aggiungendo un titolo
class Finestra {
Rettangolo r; ....
void disegnaCornice() {...}
void disegnaContenuto() {...}
}
class FinestraConTitolo extends Finestra { String titolo;
void disegnaTitolo() {...}
Sottoclassi
■ FinestraConTitolo è una sottoclasse di Finestra.
■ Una sottoclasse eredita tutte le variabili ed i metodi della sopraclasse.
Ereditarietà
■ Gli oggetti della classe Finestra sono costituiti da
■ Gli oggetti della classe FinestraConTitolo sono costituiti da
variabili
Rettangolo r;
metodi
void disegnaCornice() {...}
void disegnaContenuto() {...}
variabili
Rettangolo r;
String titolo;
metodi
void disegnaCornice() {...}
Extends (Java)
■ E` la parola chiave per definire una gerarchia di classi
class ClassePadre { […]
}
class ClasseFigliaUno extends ClassePadre { […]
}
class ClasseFigliaDue extends ClassePadre { […]
ClassePadre
ClasseFigliaUno ClasseFigliaDue
Ereditarieta`
Extends (Java)
■ Superclasse, classe base, classe padre
■ Sottoclasse, classe derivata, classe figlia
■ Le sottoclasse hanno le funzionalita` delle proprie sopraclassi piu` altre specifiche
■ Specializzazione
■ Un elemento di una sottoclasse e` anche un elemento di una sua sopraclasse
class B extends A x ∈∈∈∈ B ⇒⇒⇒⇒ x ∈∈∈∈ A
A A B
Controllo statico dei tipi
■ Java, come molti altri linguaggi, effettua un controllo dei tipi (type checking) statico.
■ Statico: fatto dal compilatore prima di iniziare l'esecuzione del programma.
■ Dinamico: fatto dall'interprete durante l'esecuzione (a runtime)
■ Il type checking statico garantisce che non ci saranno errori durante l'esecuzione.
Controllo statico dei tipi
■ Type checking statico: il compilatore controlla che per una variabile si chiami un metodo definito per la classe di
Finestra f;
FinestraConTitolo ft;
...
ft.disegnaCornice();
f.disegnaCornice();
ft.disegnaTitolo();
f.disegnaTitolo();
errore di compilazione
f è una finestra e non ha il metodo disegnaTitolo
corretto
Tassonomie
■ Spesso l'ereditarietà è utilizzata per rappresentare tassonomie (classificazioni)
Sottotipi
■ Una sottoclasse può essere vista come l'implementazione di un sottotipo.
■ L'ereditarietà realizza una relazione is-a (è un).
■ Un rettangolo è un poligono.
■ Un rettangolo ha tutte le operazioni di poligono (eventualmente
Ereditarietà singola
■ Ogni sottoclasse ha una sola sopraclasse.
■ Struttura ad albero.
■ In Java la classe Object è la radice della gerarchia.
■ Qualunque oggetto è un Object.
■ I tipi primitivi non sono Object.
Ereditarietà multipla
■ In Java non è permessa.
■ Dà maggiore espressività ma crea problemi di conflitti e duplicazioni.
ApparecchioElettrico tensione
potenza
Articolo codice prezzo
Televisore pollici
Polimorfismo
■ POLIMORFISMO: proprietà di un oggetto di avere più di un tipo, in accordo alla relazione di
ereditarietà.
Esempio:
■ D sottoclasse di B e di A
■ un oggetto D è un B ed è un A
■ un oggetto di tipo (classe) D è
anche un oggetto di tipo (classe) B e anche un oggetto di tipo (classe) A
Polimorfismo
A a; B b; D d;
d = new D();
b = new D();
a = new D();
a = b;
questi
assegnamenti sono tutti legali perché un
oggetto di tipo D ha anche tipo B e A
Polimorfismo
■ L'assegnamento x = espr è legale per il compilatore se:
◆ A uguale a B
◆ B sottoclasse (sottotipo) di A
dove A è il tipo di x e B è il tipo di espr
■ Analogamente se x è un parametro formale di un metodo e espr è il parametro attuale (della chiamata)
■ Controllo statico.
Upcasting
■ Upcasting: ci si muove da un tipo specifico ad uno più generico (da un tipo ad un suo “sopratipo”).
■ L’upcasting è sicuro per il type checking: dato che una
sottoclasse eredita tutti i metodi delle sue sopraclassi, ogni messaggio che può essere inviato ad una sopraclasse può anche essere inviato alla sottoclasse senza il rischio di
A a; B b; D d;
d = new D();
b = d;
a = d;
d viene visto come se fosse un oggetto di tipo B d viene visto come se fosse un oggetto di tipo A
Upcasting
Poligono p;
Rettangolo r;
...
p.disegna();
r.disegna();
p.perimetro();
r.perimetro();
corretto per il compilatore
Upcasting
Poligono p;
Rettangolo r;
...
r.diagonale();
p.diagonale();
errore di
compilazione
p è di tipo Poligono e non ha il metodo diagonale
corretto
Upcasting
Poligono p;
Rettangolo r;
...
p = r;
r.diagonale();
p.diagonale();
Il compilatore dà errore ma, se si facesse il controllo a runtime, p.diagonale()
sarebbe corretto perché p è legata ad un rettangolo (che possiede il metodo
diagonale).
Visto da p, il rettangolo è un poligono.
p r
Rettangolo
Upcasting
Poligono p;
Rettangolo r;
p = r;
r.diagonale();
p.diagonale();
errore di
compilazione
p è di tipo Poligono e non ha il metodo diagonale
corretto
se si facesse il controllo a runtime,
sarebbe corretto perché p è legata ad un
Overriding
■ Una sottoclasse può anche ridefinire (OVERRIDING) un metodo della sopraclasse.
class Finestra {
Rettangolo r; ....
void disegnaCornice() {...}
void disegnaContenuto() {...}
}
class FinestraConTitolo extends Finestra { String titolo;
void disegnaCornice() { … disegna la cornice con il titolo …}
}
Cella (1)
class Cella {
int contenuto=0;
int get() {return contenuto;}
void set(int n) {contenuto=n;}
}
class CellaConMemoria extends Cella { int backup=0;
void set(int n) {backup=contenuto;
contenuto=n;}
void restore() {contenuto=backup;}
}
Ereditarietà da Object (Java)
class Complex { double re,im;
...
}
Complex c = new Complex(1.5, 2.4);
System.out.println(c);
c viene convertito in stringa con il metodo toString definito in Object.
Si ottiene: Complex@...
Si può ridefinite toString
class Complex { ...
String toString() {
return(re + " + i " + im);
} }
Con la stampa si ottiene:
1.5 + i 2.4
Come estendere un metodo
■ Spesso un metodo di una sottoclasse definito per
overriding non ridefinisce completamente il metodo con lo stesso nome della sua sopraclasse, ma lo estende.
■ Ad esempio, il metodo disegnaCornice della
FinestraConTitolo estende il metodo disegnaCornice della Finestra, con del codice specifico per disegnare il titolo.
■ Per evitare di duplicare il codice, si può far riferimento ad un metodo della sopraclasse con lo stesso nome mediante la notazione super.
super
Per ridefinire un metodo in modo incrementale:
class FinestraConTitolo extends Finestra { String titolo;
void disegnaCornice() { super.disegnaCornice();
... nuovo codice ...
} }
super.disegnaCornice() chiama il metodo
disegnaCornice della sopraclasse (che altrimenti non sarebbe visibile).
Cella (2)
class Cella {
int contenuto=0;
int get() {return contenuto;}
void set(int n) {contenuto=n;}
}
class CellaConMemoria extends Cella { int backup=0;
void set(int n) {backup=contenuto;
super.set(n);}
void restore() {contenuto=backup;}
}
Super come costruttore della sopraclasse
■ Super è utilizzata per chiamare un costruttore della sopraclasse
■ La chiamata di
this(… ) o super(…) deve essere la prima istruzione del
costruttore!
■ Se è omessa la chiamata di un costruttore sella sopraclasse viene sempre chiamato
class ClasseSopra {
public ClasseSopra(int x) {
System.out.println("Costruttore ClasseSopra: " + x);
}
public ClasseSopra() { this(-1);
System.out.println("Costruttore ClasseSopra");
} }
class ClasseSotto extends ClasseSopra { public ClasseSotto (int x) {
super(x);
System.out.println("Costruttore ClasseSotto");
}
public ClasseSotto() { super()super();;
System.out.println("ostruttore ClasseSotto");
}
Upcasting
Poligono p;
Rettangolo r;
...
p = r;
p.disegna();
p.perimetro();
corretto per il compilatore, ma
quale metodo si esegue?
Quello di Poligono o
Binding dinamico
■ Un oggetto decide quale metodo applicare a se stesso in base alla propria posizione nella gerarchia dell’ereditarieta`
■ Binding dinamico: decidere a tempo di esecuzione quale metodo applicare
■ Binding statico: decidere a tempo di compilazione quale funzione applicare (Pascal, C, …)
Java adotta il binding dinamico
Binding dinamico
p.perimetro();
Si esegue il metodo perimetro dell'oggetto a cui p fa riferimento in quel momento.
Poligono p = new Poligono();
Rettangolo r = new Rettangolo();
p.perimetro();
p = r;
p.perimetro();
si esegue il metodo perimetro di Poligono
si esegue il metodo
perimetro di Rettangolo
Binding dinamico
■ In questo contesto:
■ Binding: legame fra il nome di un metodo in una invocazione e (codice del) metodo.
■ obj.m(): quale metodo m viene eseguito?
■ Nei linguaggi tradizionali le chiamate di procedura vengono risolte dal compilatore.
■ Nei linguaggi ad oggetti (tranne il C++) le chiamate di metodi sono risolte dinamicamente.
■ BINDING DINAMICO: la forma di un oggetto determina dinamicamente quale versione di un metodo applicare.
Binding dinamico
class Finestra {
Rettangolo r; ....
void disegnaCornice() {...}
void disegnaContenuto() {...}
void rinfresca() {
this.disegnaCornice();
this.disegnaContenuto();
} }
class FinestraConTitolo extends Finestra { String titolo;
void disegnaCornice() { … disegna la cornice con il titolo …}
Binding dinamico
void rinfresca() {
this.disegnaCornice();
this.disegnaContenuto();
}
this (che può anche essere omesso) si riferisce sempre all’oggetto corrente.
FinestraConTitolo ft;
...
ft.rinfresca();
chiama il metodo disegnaCornice di FinestraConTitolo
Overriding
Ricerca di un metodo per un oggetto:
■ la sottoclasse verifica se possiede o meno un metodo con tale nome e con gli stessi parametri (stessa signature), se si`, lo usa
■ se no, la classe padre cerca per la classe figlia tale metodo tra quelli in essa definiti (e cosi` via nella gerarchia)
■ Un metodo definito in una sottoclasse e avente la stessa signature di un metodo di una delle classi antenate
nasconde il metodo di quest’ultima alla sottoclasse
Overloading, overriding, …
■ Firma o
signature: nome + lista dei tipi dei parametri
■ Overloading:
stesso nome ma diversa lista dei tipi di parametri
■ Overriding:
stessa firma ma classi diverse (purché nella stessa
gerarchia)
class ClasseA {
public void metodo (int x) {
System.out.println("Metodo di Classe A: " + x);
}
public void metodo() {
System.out.println("Metodo di Classe A");
this.metodo(-1);
} }
class ClasseB extends ClasseA { public void metodo (int x) {
System.out.println("Metodo di Classe B esteso");
super.metodo(x);
}
public void metodo() {
System.out.println("Metodo di Classe B esteso");
super.metodo();
}
OVERLOADING
OVERLOADING OVERRIDING OVERLOADING
Overriding di metodi privati
■ Se invio il messaggio metodo1 ad un oggetto di tipo ClasseDue quale metodo metodo2 viene eseguito?
■ Binding dinamico?
■ Dichiarare che un metodo è privato equivale a “cambiargli nome”
■ In effetti a parte ClasseUno nessuno sa o deve sapere dell’esistenza di metodo2 di
class ClasseUno {
public void metodo1 () {
System.out.println("Eseguito metodo 1 nella classe Uno");
this.metodo2();
}
private void metodo2() {
System.out.println("Eseguito metodo 2 nella classe Uno");
} }
class ClasseDue extends ClasseUno { public void metodo2 () {
System.out.println("Eseguito metodo 2 nella classe Due");
}
Overriding di campi
■ È possibile ridefinire dei campi
MA
■ Ai campi non si applica il binding dinamico!
class ClassePadre { public int x = 1;
public int y = 2;
public void getValue() {
System.out.println("Valore della x: "
+ this.x);
System.out.println("Valore della y: ” + this.y);
} }
class ClasseFiglia extends ClassePadre { public String x = “3”;
public int z = 4;
public void getValue() { super.getValue();
System.out.println("Valore della x estesa: " + this.x);
System.out.println("Valore della z: ” + this.z);
Overriding di variabili nei blocchi
■ È possibile definire variabili locali ad un blocco
MA
■ A differenza del C o del
Pascal, in un blocco interno Java non è possibile ridefinire una variabile esterna
class ProvaVariabiliLocali {
public static void main(String[] args) {
{
int z = 2;
}
System.out.println(z);
int x = 1;
if (x > 0) {
int y = 3;
} else {
int x = 2;
int y = 5;
}
Riuso del software
■ La programmazione ad oggetti consente di utilizzare classi già esistenti per produrre nuovo software:
■ Uso.
◆ Un oggetto comunica con oggetti di altre classi
■ Composizione.
◆ Si definiscono nuove classi i cui oggetti sono composti di oggetti di classi già esistenti.
■ Ereditarietà.
◆ Favorisce lo sviluppo incrementale, estendendo classi già esistenti.
Composizione
class Automobile { int lunghezza;
Motore motore;
Ruota[] ruote;
...
}
class Motore {
int cilindrata;
...
}
Automobile miaAuto = new Automobile();
...
class Ruota {
double pressione;
int diametro;
...
}
Ereditarietà vs composizione
AppElettr Articolo
Televisore
Televisore è un apparecchio elettrico e è un articolo.
La sottoclasse eredita il
comportamento di altre classi.
Automobile
Motore Ruote
Carrozzeria
L'automobile ha un motore, una carrozzeria, …
L'automobile non ha il
comportamento del motore, ...
Programmare con l'ereditarietà
■ Si consideri una figura composta di diverse forme geometriche (linee, rettangoli, cerchi, …).
■ Nella programmazione
tradizionale, una procedura per disegnare la figura dovrebbe considerare tutti i casi.
■ Se si aggiunge la forma
TRIANGOLO occorre modificare la procedura disegna.
void disegna(figura f) { for ogni forma S in f switch(S.genere) case LINEA:
disegnaLinea() case RETTANGOLO:
disegnaRettangolo() case CERCHIO:
disegnaCerchio()
… }
Programmare con l'ereditarietà
class Figura {
Forma[] s; //una figura è inplementata //come un array di Forme
void disegnaFigura() {
for (int i=0; i<s.length; i++) s[i].disegna();
Programmare con l'ereditarietà
■ Se si aggiunge la forma Triangolo, è sufficiente definire una nuova sottoclasse di Forma con il metodo disegna:
■ La classe Figura non viene modificata.
■ Il binding dinamico fa sì che, se la figura contiene un triangolo, venga chiamato il metodo per disegnare un triangolo.
class Triangolo extends Forma { ...
void disegna(){...} }
Classi astratte
■ In un programma non ci si aspetta di chiamare il metodo disegna della classe Forma, né di usare oggetti di questa classe.
■ La classe Forma serve solo per avere una interfaccia comune per le varie forme specifiche.
Classi astratte
abstract class Forma {
abstract void disegna();
}
class Linea extends Forma { void disegna() { ...}
}
....
Forma f = new Forma();
Forma f = new Linea();
Errore di compilazione.
Non si possono creare og- getti di una classe astratta.
Non c'è la implementa- zione del metodo
Alcuni metodi (dichiarati abstract) non sono implementati
Es.: Poligoni
■ perimetro, area sono piu` volte ridefinite nella gerarchia di classe
Es.: Prisma
■ I metodi volume, superficieLaterale e SuperficieTotale sono polimorfi
Interfacce
■ Una classe astratta pura costituisce una interfaccia.
■ In Java può essere definita anche come interface.
interface Forma { void disegna();
}
class Linea implements Forma { void disegna() { ...}
} ...
Forma f = new Linea();
Interfacce vs. classi astratte
■ Le classi astratte possono essere miste, ossia possono contenere anche metodi non astratti.
■ Con l'ereditarietà singola di Java, una classe può essere sottoclasse solo di una classe astratta.
■ Le interfacce invece non sono soggette al vincolo della struttura gerarchica ad albero.
■ Una classe può implementare più di una interfaccia (qualche analogia con ereditarietà multipla).
Interfacce multiple
interface A {
void metodoA();
}
interface B {
void metodoB();
}
class C implements A,B {
void metodoA() {System.out.println("sono A");}
void metodoB() {System.out.println("sono B");}
public static void main(String[] args) {
A a = new C();//l’oggetto è visto come un A B b = new C();//l’oggetto è visto come un B a.metodoA();
b.metodoB();
Classi generiche
■ La proprietà che tutte le classi sono discendenti di Object
consente di definire strutture dati che possono contenere oggetti di qualunque tipo.
■ Non si può specificare il tipo degli elementi della pila:
possono essere oggetti qualunque.
class Pila { ...
void push(Object x) {...};
Object pop() {...};
}
Pila s; Linea l; Rettangolo r;
...
s.push(l); s.push(r);
Downcasting
Supponiamo di sapere che sulla pila vengono messi solo rettangoli. Come possiamo utilizzare gli oggetti estratti dalla pila?
class Pila { ...
void push(Object x) {...};
Object pop() {...};
}
Pila s; Rettangolo r;
...
s.push(new Rettangolo());
r = s.pop(); Errore di compilazione.
pop restituisce un Object, che è più generale di Rettangolo
Downcasting
■ Downcasting: ci si muove da un tipo più generale ad uno più specifico (da un tipo ad un sottotipo)
■ Se A è un sottotipo di B e se espr ha tipo B, (A) espr ha tipo A
■ L’assegnamento
A a = (A) espr è corretto per il compilatore
■ (A) espr può dare errore a run time, se l’oggetto ottenuto valutando espr non ha tipo A.
Downcasting
class Pila { ...
void push(Object x) {...};
Object pop() {...};
} ...
Pila s; Rettangolo r;
...
s.push(new Rettangolo());
r = (Rettangolo) s.pop();
Accettato dal compilatore.
Può dare errore a run time.
Controllo a run-time.
Quando si esegue questa istruzione
si controlla che l'oggetto restituito
da pop sia veramente un rettangolo.
Non e` un cast alla “C” !