Il linguaggio Java e la progettazione di software object-oriented Java 1 ANTEFATTO/MOTIVAZIONE: un (cattivo) esempio • Per realizzare un'applicazione gestionale, di una certa dimensione, vari programmatori sono incaricati di realizzarne parti diverse • Alberto è incaricato di programmare l'interfaccia utente, e di definire il tipo Data per memorizzare le date. • Bruno utilizza il tipo Data (definito da Alberto) per scrivere la parte di applicazione che gestisce paghe e contributi Java 2 Codice C di Alberto Codice C di Bruno •La manipolazione dei dati avviene con accesso diretto alla rappresentazione del tipo: typedef struct { int giorno; int mese; int anno; } Data; Data d; … d.giorno=14; inizializzazione di un d.mese=12; oggetto di tipo Data d.anno=2000; … uso if (d.giorno == 27) di un paga_stipendi(); oggetto if (d.mese == 12) di tipo data paga_tredicesime(); … void stampaData(Data d) { printf("%d", giorno); if (d.mese == 1) printf("Gen"); else if (d.mese == 2) printf("Feb"); … printf("%d", anno); } } Java 3 La definizione di Data cambia! • Dopo avere rilasciato una prima versione, Alberto modifica la rappresentazione per semplificare “stampaData” typedef struct { int giorno; char mese[4]; int anno; } Data ; void stampaData(Data d) { printf("%d", d.giorno); printf("%d", d.mese); printf("%d", d.anno); } Java 4 Disastro! • Il codice scritto da Bruno non funziona più! • Occorre modificarlo: Data d; … if (d.giorno == 27) paga_stipendi(); if (strcmp(d.mese,"Dic")) paga_tredicesime() … Java 5 Le modifiche sono costose • Se la struttura dati è usata in molti punti del programma, una modifica piccola della struttura comporta tante piccole modifiche. • Fare tante (piccole) modifiche è: – fonte di errori – lungo e costoso – difficile se documentazione cattiva • La realizzazione interna (implementazione) delle strutture dati è una delle parti più soggette a cambiamenti (per maggiore efficienza, semplificazione codice...)! Java 6 Soluzione • Il problema può essere risolto disciplinando o impedendo l'accesso alla realizzazione della struttura dati. – Bruno non deve più scrivere: (d.mese == 12) Java 7 Come usare una Data? • Se si impedisce a Bruno l'accesso ai campi della struttura Data, come può Bruno utilizzare le date di Alberto? struct Data { int giorno; int mese; int anno }; • Il responsabile per Data (Alberto) deve fornire delle operazioni sufficienti agli utilizzatori o clienti del tipo Data (Bruno) Java 8 Funzioni predefinite per Data • L’accesso alla struttura dati è fornito unicamente tramite operazioni predefinite In C: void inizializzaData(Data *d, int g, int m, int a) { d->giorno = g; { d->mese = m; d->anno = a; } int leggi_giorno(Data d) {return(d.giorno);} int leggi_mese(Data d){return(d.mese);} int leggi_anno(Data d){return(d.anno);} Java 9 Uso delle operazioni predefinite • Il codice di Bruno è scritto in modo tale da usare solo le operazioni predefinite: Data d; … inizializzaData(*d, 14,12,2000); if (leggi_giorno(d) == 27) paga_stipendi(); if (leggi_mese(d) == 12) paga_tredicesime() … Java 10 Modifica struttura dati • Se Alberto cambia la rappresentazione interna di Data, deve modificare anche le operazioni predefinite (ma lasciandone immutato il prototipo): int leggi_mese(Data d){ if (strcmp(d.mese,"Gen")) return(1); … else if (strcmp(d.mese,"Dic")) return(12); } void inizializzaData(Data *d, int g, int m, int a)) {d->giorno = g; if (m==1) d->mese = "Gen"; … } • Ma il codice di Bruno non cambia! Java 11 Incapsulamento • Abbiamo INCAPSULATO la struttura dati! • Ogni modifica alla struttura resta confinata alla struttura stessa • Le modifiche sono – più semplici – più veloci – meno costose Java 12 Tipo di dato astratto (ADT) • Astrazione sui dati che non classifica i dati in base a loro rappresentazione (IMPLEMENTAZIONE), ma in base a loro comportamento atteso )(SPECIFICA). • Il comportamento dei dati è espresso in termini di un insieme di operazioni applicabili a quei dati (INTERFACCIA) • Le operazioni dell'interfaccia sono le sole che si possono utilizzare per creare, modificare e accedere agli oggetti – L'implementazione della struttura dati non può essere utilizzata al di fuori della definizione della struttura. • ADT incapsula tutte le operazioni che manipolano i valori del tipo: – esporta • il nome del tipo • l'interfaccia: tutte e sole le operazioni per manipolare oggetti (cioè dati) del tipo e la loro specifica – nasconde • la struttura del tipo • l'implementazione delle operazioni Java 13 Progettare l'interfaccia • Interfaccia di un ADT: – "promessa" ai clienti del tipo che le operazioni saranno sempre valide, anche in seguito a modifiche – deve includere tutte le operazioni utili... •giorno_dopo: passa al giorno successivo: in C sarebbe: void giorno_dopo(Data *d){ d->giorno++; if (d->giorno > 31){/*ma mesi di 30, 28, 29?*/ d->giorno = 1; d->mese++; if (d->mese > 12) { d->mese = 1; d->anno++; } } } • Se la funzione non fosse definita da Alberto, Bruno avrebbe difficoltà a scriverla senza accedere ai campi di una Data. Java 14 ADT in C? • C non ha costrutti linguistici diretti per definire ADT (tipo abstracttypedef). • Bruno potrebbe accedere direttamente ai campi della struct che rappresentano la data. Non possiamo impedirglielo. – Ma con preprocessore, header files, puntatori, prototipi e una certa disciplina si può separare interfaccia e implementazione. • C inoltre manca di molte altre astrazioni utili per programmazione inthe-large, che si ritrovano invece nei linguaggi orientati agli oggetti Java 15 Costrutti per ADT • Esiste un certo numero di linguaggi di programmazione ("orientati agli oggetti") che: – obbligano ad incapsulare le strutture dati all’interno di opportune dichiarazioni (CLASSI); – consentono facilmente di impedire l'accesso all'implementazione. Java 16 Il linguaggio Java • Definito dalla Sun Microsystems nel 1995/96. Useremo la versione J2SE 1.5 (Java 2 Standard Edition) • Esistono anche J2EE (Java 2 Enterprise Edition) – costruita sopra J2SE, offre servlets e JSP, EJB… – per sviluppare applicazioni "server side" • J2ME (Java 2 Micro Edition) – per piccoli dispositivi (telefoni, PDA,…) Java 17 Caratteristiche • Object-oriented (OO) • Distribuito – RMI • Indipendente dalla piattaforma – Bytecode e Java Virtual Machine • Sicuro – Esecuzione in una "sandbox" che garantisce che non si possa dannegiare l'host Java 18 Programmazione “in-the-small” e programmazione “in-the-large” • Programmazione “in piccolo”(in-the-small) – strutturazione del codice per mezzo di astrazioni procedurali – adatta all’implementazione di dettaglio di piccoli programmi – non adatta al progetto di sistemi di grosse dimensione • Programmazione “in grande” (in-the-large) – astrazione dai dettagli implementativi per concentrarsi sulla struttura del sistema (information hiding) – il concetto di ADT diventa fondamentale – i linguaggi orientati agli oggetti (per esempio, Java) supportano esplicitamente la programmazione in grande – usa i principi della programmazione in-the-small per rifinire i dettagli implementativi Java 19 Quadro d'insieme • Programmazione “in the small” – Tipi primitivi – Dichiarazione di variabili – Strutture di controllo: selezione condizionale, cicli • sono identiche a quelle del C: if, switch, while, for... – Array (differenze col C) • Programmazione “in the large” – Classi – Interfacce – Packages Java 20 Sorpresa! • Non esistono i "tradizionali" concetti di – "programma" – "sottoprogramma" (funzione o procedura) • Esistono le classi, che raggruppano (tra le altre cose) dati privati e metodi – i metodi sono il termine OO usato per i sottoprogrammi (hanno sintassi analoga alle funzioni C) – vedremo tra breve Java 21 Un’anticipazione: il concetto di classe ADT in Java Java 22 La struttura di un programma Java - nozioni preliminari • Un programma Java è organizzato come un insieme di classi – Ogni classe corrisponde a una dichiarazione di tipo o a una collezione di funzioni – Classe contiene dichiarazioni di variabili (attributi) e di funzioni (metodi) • Il programma principale è rappresentato da un metodo speciale di una classe, detto main Java 23 Il primo programma Java file: HelloWorld.java public class HelloWorld { public static void main(String args[]){ System.out.println(“Hello world!”); } } Java 24 Esempio di classe in Java class Data { private int giorno; private int mese; private int anno; ... public int leggi_giorno(){ //@ ensures (*restituisce il giorno*); public int leggi_mese(){ //@ ensures (*restituisce il mese*); public int leggi_anno(){ //@ ensures (*restituisce l’anno*); } • class è come struct, ma alcuni campi possono essere funzioni. Dati e funzioni sono incapsulati. I private sono “invisibili all’esterno”, mentre i “public” no. Java 25 Il costrutto class • Una classe può essere vista come un tipo definito dall’utente che specifica le operazioni utilizzabili sul tipo stesso. Il tipo può essere usato per dichiarare altre variabili. int a, b; – dichiara due variabili a e b sulle quali è possibile fare tutte le operazioni predefinite per il tipo int. Data d; – dichiara una variabile d sulla quale è possibile fare tutte le operazioni definite nella classe Data • Definisce proprio un ADT. Java 26 Classi, istanze (esemplari), oggetti • In un programma OO si dichiarano classi per potere definire degli oggetti • Un oggetto è una entità “viva”, che può essere e modificata e letta tramite operazioni • Una classe è una sorta di “stampo” per creare oggetti simili ma distinti. – Es. Una classe Data permette di • dichiarare variabili di tipo Data • Creare oggetti di tipo Data (oggetti e variabili non sono la stessa cosa...) • Un oggetto è detto istanza (o esemplare) della classe che abbiamo usato come stampo per crearlo • Si dice anche che l’oggetto appartiene alla classe Java 27 Attributi di una classe • La struttura dati di una classe è definita dai suoi attributi Java ATTRIBUTI class Data { private int giorno; private int mese; private int anno; ... – giorno, mese, anno sono gli attributi di Data 28 Oggetti • Tutti gli oggetti della stessa classe hanno la stessa struttura – Il numero e tipo dei loro attributi di istanza è lo stesso – Ad esempio, la classe Data definisce tutte le date possibili, tramite gli attributi giorno, mese, anno. • Ogni oggetto, in ogni istante dell’esecuzione del programma, è caratterizzato da uno stato, che è dato dal valore degli attributi dell’oggetto – Lo stato dell’oggetto data1 di tipo Data è definito dal valore degli attributi giorno, mese, anno nome dell’oggetto nome della classe di cui l’oggetto è istanza data1 Data nome e valore degli attributi giorno = 30 mese = 12 anno = 2001 Java 29 Oggetti • Stato di un oggetto – Lo stato rappresenta la condizione in cui si trova l’oggetto – Lo stato è definito dai valori delle variabili interne all'oggetto (attributi) • Comportamento di un oggetto – Determina come agisce e reagisce un oggetto – È definito dall’insieme di operazioni che l’oggetto può compiere (metodi) Java 30 Metodi di una classe M E T O D I • Le operazioni definite nella classe sono chiamati i metodi della classe class Data { ... public int leggi_giorno(){...}; public int leggi_mese(){...}; public int leggi_anno(){...}; ... } Java 31 Definizione di metodi • I metodi definiscono il comportamento degli oggetti appartenenti ad una classe • Ogni metodo è definito come segue: <tipo val. rit.> <nome.>([<dic. par. formali>]) { <corpo> } • Il tipo void viene utilizzato per metodi che non restituiscono alcun valore • (La nozione di metodo ha forti analogie con quella di funzione del linguaggio C) Java 32 Accesso ad attributi e metodi • Tramite la "notazione punto" • Esempio: Data d; int x; ...//codice che inizializza d x = d.leggi_giorno(); – Esegue leggi_giorno() su oggetto d: restituisce valore del giorno della data d. – E’ come se leggi_giorno() avesse come argomento implicito d: in C scriveremmo: leggi_giorno(d); – Si dice anche che all’oggetto d inviamo il messaggio leggi_giorno Java 33 Qualche cambiamento... • Cambia la sintassi... – Data d; ... if (d.giorno_di_paga()) paga_stipendi(); if (d.mese_13esima()) paga_tredicesime() ... – L’accesso ad attributi e metodi di un oggetto si effettua tramite la "notazione punto" • .. e la terminologia – int a; -– Data d; -- analogia con le struct del C a è una variabile di tipo int d è una variabile di classe Data Java 34 Oggetti mutabilii • Lo stato degli oggetti (mutabili) può cambiare nel tempo, chiamando metodi opportuni data1 data1 Data Data giorno = 30 mese = 12 anno = 2001 data1.giorno_dopo() giorno = 31 mese = 12 anno = 2001 • NB: Esistono alcuni casi in cui gli oggetti sono immutabili (cioè non possono essere modificati), come ad es. la classe predefinita String Java 35 Il metodo giorno_dopo() • Per modificare lo stato, il metodo deve potere accedere ai campi dell’oggetto su cui è stato chiamato: • Nella definizione di un metodo ci si può riferire direttamente (senza notazione punto) ad attributi e metodi dell’oggetto sul quale il metodo sia stato invocato • class Data {... public void giorno_dopo(){ //@ ensures (*incrementa la data*) giorno++; if (giorno > 31){ giorno = 1; mese++; if (mese > 12) { mese = 1; anno++;}}} … Data d; … d.giorno_dopo(); /*modifica lo stato dell’oggetto d*/ Java 36 Private e Public • Attraverso i metodi "public" di una classe è possibile vedere qual’è lo stato di un oggetto ... – Data d; int x; ... x = d.leggi_giorno(); • …ma non accedere ai dati "private" (al di fuori del codice di Data): if (d.mese ==12)... **Errore di compilazione!** • Il costrutto di classe consente quindi di definire un tipo di dato astratto. Java 37 Programmazione "in the small" Java 38 Tipi primitivi e variabili • Tipi numerici: – – – – – – byte: short: int: long: float: double: 64 bit Dichiarazione di variabili 8 bit 16 bit 32 bit 64 bit 32 bit byte un_byte; int a, b=3, c; char c=‘h’, car; boolean trovato=false; • Altri tipi: – boolean: – char: true false 16 bit, carattere Unicode Java 39 Variabili e tipi riferimento • Tutte le variabili hanno un tipo dichiarato • Le variabili di tipo primitivo (numerico, char, bool) contengono il valore • Tutte le altre contengono riferimenti a valori (ecco perché si dicono essere "di tipo riferimento") • I tipi riferimento sono: – – – – Tipi definiti dall’utente (Classi, Interfacce) Tipi array Enumerazioni Le classi sono anche dette tipi riferimento: possono essere usate per dichiarare variabili che contengono riferimenti – tipi riferimento possono essere utilizzati ovunque si usi un tipo: dichiarazioni di variabili e parametri, tipo del valore restituito da una funzione... • Le variabili – consentono di accedere agli oggetti – sono allocate nello stack a run-time quando si chiama il sottoprogramma in cui sono dichiarate • (gli oggetti referenziati dalle variabili sono invece allocati sullo heap) – sono deallocate quando il sottoprogramma ritorna al chiamante Java 40 Ancora su “tipo riferimento” • Ogni variabile dichiarata con una classe contiene un riferimento a un oggetto: un indirizzo di memoria – il valore effettivo dell’indirizzo non è noto e non interessa • Un oggetto è memorizzato in una opportuna area di memoria – d, di tipo Data,contiene l’indirizzo della prima cella dell’oggetto d 3 11 1991 xxx Java giorno mese anno 41 Dichiarazione e inizializzazione • La dichiarazione di una variabile di tipo riferimento non alloca spazio per un oggetto, ma solo per il riferimento ad un oggetto • A una variabile di tipo riferimento è assegnato inizialmente il riferimento null, per indicare che la variabile non è ancora associata ad alcun oggetto – Es. Data d; d vale null: non esiste ancora un oggetto di tipo Data. • Un parametro o una variabile locale non possono essere usati senza essere inizializzati (compilatore rileva staticamente mancanza di inizializzazione) • In entrambi i casi occorre costruire un oggetto e inizializzarlo esplicitamente Java 42 New • La costruzione di un oggetto si realizza dinamicamente tramite l’operatore new • Esempio: Data d = new Data(); Effetto di new: • Costruisce un nuovo oggetto di tipo Data • Restituisce il riferimento all’oggetto appena creato • Il riferimento viene conservato nella variabile d per potere usare l’oggetto • Data() è un metodo particolare (ha lo stesso nome della classe, si riconosce come metodo dallE parentesi “()”) chiamato COSTRUTTORE Java 43 Differenze tra dichiarazione e creazione Data data; data = new Data( ); data = new Data( ); data Creato Creatocon conlala prima primanew. new. Data Data Java Creato Creatocon conlalaseconda secondanew. new.IlIl riferimento riferimentoalalprimo primooggetto oggetto Data è perso. Data è perso. Non Nonc’è c’èpiù piùmodo mododidiaccedere accedere all’oggetto. all’oggetto. L’oggetto L’oggettoverrà verràdistrutto distruttodal dal garbage collector. garbage collector. 44 La visione a "run-time" i m k y 6 [0, 0, 0] "abcd" h una persona Dichiarazioni all'interno di un sottoprogramma int i = 6; int [ ] m = {0, 0, 0}; String k = "abcd"; Persona y = new Persona(); String h; h=k; crescita dello stack Java 45 Condivisione (sharing) • Un oggetto è condiviso tra due variabili se entrambe accedono a esso • L'assegnamento di variabili di tipo riferimento genera condivisione (vedi k e h nell'esempio precedente) • Se oggetti condivisi sono modificabili, le modifiche apportate attraverso una variabile sono visibili anche attraverso l'altra (side effect) Java 46 Conseguenze allocazione stack vs. heap • Quando un metodo termina, tutte le variabili del corrispondente record di attivazione sono distrutte • ...pero’ gli oggetti creati sullo heap NON sono necessariamente distrutti public static Data foo() { Data d = new Data(1,1,1990); d xxx return d; } public static void main(String args[]) { Data x = foo(); x xxx ... 1 1 1990 /*d esiste solo sullo stack durante l’esecuzione di foo(), ma viene deallocata quando foo() termina. Ma l’oggetto costruito continua a vivere sullo heap!!!*/ Java 47 Tipi array • Dato un tipo T (predefinito o definito dall’utente) un array di T è definito come: T[] • Similmente sono dichiarati gli array multidimensionali: T[][] T[][][] … • Esempi: int[] float[][] Persona[] Java 48 Tipi array: dichiarazione e inizializzazione • Dichiarazione: int[] ai1, ai2; float[] af1; double ad[]; Persona[][] ap; • Inizializzazione: int[] ai={1,2,3}; double[][] ad={{1.2, 2.5}, {1.0, 1.5}} Java 49 Il caso degli array • In mancanza di inizializzazione, la dichiarazione di un array non alloca spazio per gli elementi dell’array • L’allocazione si realizza dinamicamente tramite l’operatore: – new <tipo> [<dimensione>] int[] i=new int[10], j={10,11,12}; float[][] f=new float[10][10]; Persona[] p=new Persona[30]; • Se gli elementi non sono di un tipo primitivo, l’operatore “new” alloca solo lo spazio per i riferimenti Java 50 Array di oggetti: Definizione A A Person[ ] person; person = new Person[20]; person[0] = new Person( ); E’ E’definita definitasolo sololalavariabile variabile person. L’array vero person. L’array veroee proprio proprionon nonesiste esiste person Java 51 Array di oggetti: Definizione Person[ ] B B person; person = new Person[20]; person[0] = new Person( ); Ora Oral’array l’arrayèèstato statocreato creatoma ma i idiversi oggetti di tipo diversi oggetti di tipo Person Personnon nonesistono esistonoancora. ancora. person 0 1 2 3 4 16 17 18 Java 19 52 Array di oggetti: Definizione Person[ ] person; person = new Person[20]; C C person[0] = new Person( ); Un Unoggetto oggettodiditipo tipopersona personaèè stato creato e un riferimento stato creato e un riferimento aatale taleoggetto oggettoèèstato stato inserito inseritoininposizione posizione0.0. person 0 1 2 3 4 16 17 18 19 Person Java 53 Array di oggetti vs. array di tipi base L’istruzione: float f[] = new float[10]; crea un oggetto di tipo array di float e alloca spazio per 10 float f 0 1 2 3 4 5 6 7 8 9 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 L’istruzione: Person p[] = new Person[10]; crea un oggetto di tipo array di Person e alloca spazio per 10 riferimenti a oggetti di tipo Person f Non viene allocato spazio per gli oggetti veri e propri 0 1 Java 2 3 4 5 6 7 8 9 54 Loop generalizzato per collezioni • Se abbiamo una collezione C di elementi di tipo T, possiamo itrerare su di essi scrivendo for (T x: C) { esegui azioni su x } • anche gli array sono collezioni, quindi … Java 55 Esempio per array int sum (int [ ] a) { int result = 0; n indica il generico elemento for (int n : a) result += n; dell'arry, NON l'indice! return result; } Java 56 I concetti fondamentali per la programmazione “in the large” • • • • • • • Classi Metodi Interfacce Information Hiding Ereditarietà Polimorfismo e binding dinamico Packages Java 57 Classi e ADT • Il costrutto di classe consente di definire tipi di dati astratti (ADT) • Un ADT permette di estendere il linguaggio con nuovi tipi di dato • Per ottenere un nuovo tipo di dato bisogna definire: – l’insieme dei possibili valori – le operazioni possibili sugli oggetti del tipo Java 58 Linguaggi orientati agli oggetti • Il costrutto di classe è caratteristico dei linguaggi orientati agli oggetti (OO) • Caratteristiche principali (ne vedremo altre) – Incapsulamento delle strutture dati – Protezione, al fine di impedire l'accesso all'implementazione (information hiding) Java 59 Definizione di una nuova classe • Una nuova classe viene definita nel seguente modo: <visibilità> class <nome classe> { <lista di definizioni di attributi, costruttori e metodi> } • Esempio: class Automobile { String colore, marca, modello; int cilindrata, numPorte; boolean accesa; } Java attributi 60 Metodi • I metodi sono procedure che – appartengono agli oggetti (se non si usa la parola chiave static—vedremo ) – hanno come parametro implicito l’oggetto cui appartengono (this) Java 61 Definizione di metodi: esempio class Automobile { String colore, marca, modello; int cilindrata, numPorte; boolean accesa; void accendi() {accesa=true;} boolean puoPartire() {return accesa;} void dipingi(String col) {colore=col;} void trasforma(String ma, String mo) { marca=ma; modello=mo; } } Java 62 Chiamata di metodi -semantica- 1. Vengono valutati i parametri attuali 2. Viene creata area dati ("record di attivazione") sullo stack • • spazio per i parametri formali (inizializzati col valore degli attuali) spazio per le variabili locali 3. Esecuzione del corpo si tratta di puntatori agli oggetti, tranne che per il caso dei tipi primitivi Java 63 Invocazione dei metodi • Regola per il passaggio parametri: – I parametri il cui tipo sia uno dei tipi semplici sono passati per copia – I parametri il cui tipo sia un tipo riferimento (classi, interfacce, array, enumerazioni) sono passati per riferimento (ovvero viene copiato il riferimento) Java 64 Passaggio parametri e reference: esempio 1 public class Date { public static void reinizializza(Date d) {// riporta d al 31/12/1999 d.day = 31; d.month = 12; d.year = 1999; } public static void main(String[] args) { Date d1 = new Date(1,1,1990); Date.reinizializza (d1); AlQuando momento chiamata: la della funzione termina, d1 e' 31/12/1999 d d1 xxx xxx 131 1 12 1990 1999 Java 65 Passaggio parametri e reference: esempio 2 public class Date {... void copiaIn(Date d) {// copia in d l'oggetto corrente d.day = day; d.month = month; d.year = year;} public static void main(String[] args) { Date d1 = new Date(3,2,1984); Date d2 = new Date(1,1,1990); d1.copiaIn(d2); la della funzione termina, d2 e' 3/2/1984 AlQuando momento chiamata: d d2 xxx xxx 13 21 1990 1984 Java d1 yyy 3 2 1984 66 Accesso ad attributi e metodi locali • Nella definizione di un metodo ci si riferisce ad attributi e metodi dell’oggetto sul quale il metodo sia stato invocato direttamente (senza notazione punto) • Esempio class Automobile { String colore; void dipingi(String col) {colore=col;} ... } Java 67 Visibilità dei nomi • Le variabili locali a un metodo (o i parametri formali) possono mascherare gli attributi della classe • Soluzione: – La pseudo-variabile this contiene un riferimento all’oggetto corrente e può essere utilizzata per aggirare eventuali mascheramenti Java 68 this per aggirare mascheramenti • La pseudo-variabile this contiene un riferimento all’oggetto corrente e può essere utilizzata per aggirare eventuali mascheramenti public class Automobile { private String colore, marca, modello; ... public void trasforma(String marca, String modello){ this.marca=marca; this.modello=modello;} } ... Automobile a = new Automobile(); a.trasforma(“Ford”, “T4”); • => a.marca diventa “Ford”, a.modello diventa “T4” Java 69 La pseudo-variabile this: esempio • class Data {... public void giorno_dopo(){ //MODIFIES: this //REQUIRES: this è una data valida //EFFECTS: incrementa la data this.giorno++; if (this.giorno > 31){ this.giorno = 1; this.mese++; … Data d1, d2; …//d1 e d2 inizializzate qui d1.giorno_dopo();Îqui in giorno_dopo this è lo stesso reference di d1. d2.giorno_dopo();Îqui in giorno_dopo this è lo stesso reference di d2. Java 70 this per restituire un reference • La pseudo-variabile this può essere utilizzata per restituire un riferimento all'oggetto corrente. • public class InsiemeDiInteri { ... public InsiemeDiInteri inserisci(int i){ …//modifica this inserendovi l’elemento i return this; //restituisce l‘insieme modificato... } …. } ….. InsiemeDiInteri x,y,z; ….//qui x e y sono inizializzate opportunamente z = (x.inserisci(2)).unione(y) //utile che inserisci restituisca insieme Java 71 Oggetti mutabili (richiamo...) • Lo stato degli oggetti mutabili può cambiare nel tempo, chiamando metodi opportuni data1 data1 Data Data giorno = 30 mese = 12 anno = 2001 data1.giorno_dopo() giorno = 31 mese = 12 anno = 2001 • NB: Esistono alcuni casi in cui gli oggetti sono immutabili (cioè non possono essere modificati), come ad es. la classe predefinita String Java 72 Oggetti immutabili • L'oggetto "abcd" (in quanto "String") è immutabile: il suo stato non cambia – non esistono operazioni che consentono di modificare lo stato di un oggetto string • In generale, invece, gli oggetti sono mutabili: int [ ] x = {0, 0, 0}; x[0] = 5; Java 73 La classe predefinita String in Java è un tipo riferimento (è definito da una classe) • Due regole base: • String – Variabile di tipo String è un oggetto, manipolato con un reference – le stringhe sono immutabili (non si possono aggiungere o togliere caratteri a una stringa, ma occorre costruirne una nuova) • (per avere stringhe mutabili si usa StringBuffer) • Per le stringhe c’è operatore di concatenamento + • Costruttori: – String() – String(String s) –… •Metodi (pubblici): –int length() restituisce la lunghezza di una stringa; –char charAt(int index) restituisce il char alla posizione index (il primo ha posizione 0) –String substring(int beginIndex) (parte da 0) Java 74 La classe String: esempio d’uso public class ProvaString { //OVERVIEW: … public static void main (String argv[]) { String a = new String(); //a è reference a stringa vuota String b = new String("Ciao“); //b è reference a stringa “Ciao”: //abbreviazione: String b = "Ciao“; String c = new String(b); //Ora c e' copia di b String d = b; //d e b sono alias System.out.println(b + " " + c + " “+ d); } } L’assegnamento d=b e’ assegnamento dei reference! Non si copia l’oggetto! Java 75 Private e public • Attraverso i metodi "public" di una classe è possibile vedere qual è lo stato di un oggetto ... – Data d; int x; ... x = d.leggi_giorno(); • …ma non accedere ai dati "private" (al di fuori del codice di Data): if (d.mese ==12)... **Errore di compilazione!** • Il costrutto di classe consente quindi di definire un tipo di dato astratto. Java 76 Overloading di metodi • All’interno di una stessa classe possono esservi più metodi con lo stesso nome purché si distinguano per numero e/o tipo dei parametri – Attenzione: Il tipo del valore di restituito non basta a distinguere due metodi (motivo: a volte un metodo può essere chiamato solo per i side-effect e valore restituito è ignorato) • Nozione di SEGNATURA in Java: l’informazione sul numero, il tipo e la posizione dei parametri; NB non include il tipo del valore restituito – Metodi overloaded devono avere segnature diverse • Utile per definire funzioni con codice differente ma con effetti simili su tipi diversi. Esempio: class prova { int max(int a, int b, int c) {...} double max(double a, double b) {...} int max(int a, int b) {...} } Ogni volta è chiamata la funzione "giusta" max(2,3,5); max(2.3, 3.14); max(2,3); Tipico esempio: i costruttori Java 77 Overloading: un esempio class C { int f() {...} C ref = new C(); int f(int x) {...} // corretto ref.f(); //distinguibile void f(int x) {...} // errato ref.f(5); //??? } Java 78 Argomenti in numero variabile • In Java 1.5 si possono creare metodi e costruttori con un numero variabile di argomenti – public void foo(int count, String... cards) { body } – “...” significa zero o piu' argomenti (qui, zero o piu' Stringhe) – Esempio di chiamata foo(13, "ace", "deuce", "trey"); – Solo l'ultimo argomento puo' essere un vararg – Possibile iterare, mediante il ciclo for generalizzato: for (String card : cards) { loop body } Java 79 Creazione e distruzione degli oggetti • Se implementazione deve essere private, unico modo per inizializzare un oggetto è specificare uno o più metodi particolari, chiamati costruttori • La creazione di un oggetto comporta sempre l’invocazione di un costruttore. • Il costruttore svolge due operazioni fondamentali, obbligandoci a definirle assieme: – l’allocazione della memoria necessaria a contenere l’oggetto – l’inizializzazione dello spazio allocato, assegnando opportuni valori • A differenza del C, in Java non è necessario deallocare esplicitamente gli oggetti. Di ciò si occupa il garbage collector, che è una routine di sistema che provvede automaticamente a liberare memoria quando serve (invece in C/C++…) Java 80 Costruttori • Un costruttore è un metodo speciale: – ha lo stesso nome della classe – il suo codice viene invocato in risposta al messaggio new – tipicamente contiene il codice necessario ad inizializzare gli attributi dell’oggetto in via di creazione – non ha un tipo del risultato (è implicito nel nome del costruttore) – Si possono definire più costruttori con lo stesso nome (ma numero o tipo dei parametri deve essere diverso): overloading dei costruttori • Vantaggio: all’allocazione si esegue sempre anche l’inizializzazione, eliminando una frequente fonte di errori. Java 81 Esempio di costruttore public class Date { private int month, day, year; public Date(int d, int m, int y) { // day month year year = y; month = m; day =d) ... } … Date d = new Date(3,12,1987); ÎCrea un oggetto d di tipo Date e lo inizializza al 3/12/1987 ÎVantaggio: il metodo Date(...) consente di inizializzare una Data. Ad esempio, potrebbe anche verificare che la data sia legale, “rifiutando” il 31/2/2002 Java 82 Costruttori di default • Se non si definisce nessun costruttore, il compilatore fornisce il costruttore di default (senza parametri), che svolge le seguenti funzioni: – Alloca lo spazio per gli attributi di tipo primitivo – Alloca lo spazio per i riferimenti agli attributi di tipo definito dall’utente – Inizializza a null tutti i riferimenti, a 0 tutte le variabili numeriche, a false tutti i boolean • Il costruttore di default (senza parametri) viene fornito dal compilatore a meno che non si definiscano altri costruttori Java 83 I costruttori: esempio public class C { private int i1; private int i2; public C() { i1 = 0; i2 = 0; } “omaggio” del compilatore: non è necessario scriverlo } .... public class Esempio { public static void main(String args[]) { C x; Corretto perchè il compilatore ha inserito x = new C(); x = new C(5,7); automaticamente il costruttore di default } } Sbagliato, non esiste nessun costruttore che prenda due parametri Java 84 Molti costruttori: esempio z public class C { private int i1; private int i2; public C(int a1, int a2) { i1 = a1; i2 = a2; } public C(int a) { i1 = a; i2 = a; } } .... public class Esempio { public static void main(String args[]) { C x, y, z; x = new C(); y = new C(1); z = new C(1,4); } } y C i1 = 1 x i2 = 1 C i1 = 1 i2 = 4 Sbagliato, visto che il programmatore ha definito un costruttore, il compilatore non aggiunge quello di default Corretto, esiste un costruttore che prende un parametro intero Corretto, esiste un costruttore che prende due parametri interi Java 85 Ulteriore esempio public class Automobile { private String colore, marca, modello; private int cilindrata, numPorte; private boolean accesa; public Automobile(String col, String mar, String mod) { colore=col; marca=mar; modello =mod; } public Automobile() {se serve e’ necessario definirlo anche se e’ come quello di default, perche’ c’e’ un altro costruttore… colore=marca=modello=null; cilindrata=numPorte=0; } public void accendi() {accesa=true;} public boolean puoPartire() {return accesa;} public void dipingi(String col) {colore=col;} } Java 86 Metodi e attributi di classe • Sintassi: static <definizione dell’attributo o metodo> • Un attributo di classe è condiviso da tutti gli oggetti della classe • Si può accedere a un attributo di classe senza bisogno di creare un oggetto tramite la notazione: <nome classe>.<nome attributo> • Un metodo di classe può essere invocato senza bisogno di creare un oggetto tramite la notazione: <nome classe>.<nome metodo>(<par. attuali>) • Un metodo di classe può essere comunque invocato su un oggetto della classe Java 87 Metodi e attributi di classe: vincoli • Un metodo static può accedere ai soli attributi e metodi static • Un metodo convenzionale può accedere liberamente a metodi e attributi static Java 88 Metodi e attributi di classe: esempio tutte le forme ("shape") vengono visualizzate su un certo schermo class Shape { static Screen screen=new Screen(); // si noti l’iniz. static void setScreen(Screen s) {screen=s;} void show(Screen s) {setScreen(s); ...} } ... Shape.setScreen(new Screen()); // corretto Shape.show(); // errato, show è un metodo normale Shape s1=new Shape(), s2=new Shape(); Screen s=new Screen(); s1.setScreen(s); // corretto, si possono chiamare // metodi static su oggetti // in questo punto s2.screen==s1.screen==s Java 89 Visione a run time shape1 shape2 shape3 Allocato Java 90 Attributi costanti • È possibile definire attributi costanti tramite la notazione: final <definizione di attributo>=<valore> • Esempio class Automobile { int colore; final int BLU=0, GIALLO=1,...; void dipingi(int colore) {this.colore=colore;} } ... Automobile a=new Automobile(); a.BLU=128; // errato System.out.println(“BLU=“+a.BLU); // corretto Java 91 Ritorniamo al concetto di sharing definisce riferimento d a una Data, senza inizializzarlo Data d; Alloca in memoria, in celle consecutive, attributi di Data. d1 diventa l’indirizzo della prima cella Data d1= new Data(); d diventa uguale a d1: l’indirizzo contenuto in d diventa uguale all’indirizzo contenuto in d1 (diventano alias, cioè nomi diversi per la stessa area di memoria d=d1; anche d.giorno diventa 1, poiché d e d1 sono alias d1.giorno =1; d xxx d1 xxx 1 .. .. Java giorno mese anno 92 Reference e operatore “== ” •L’operatore di confronto == confronta i valori dei reference. non gli oggetti! d giorno 1 xxx Data d = new Data(1,12,2001); mese 12 Data d1= new Data(1,12,2001); 2001 anno if (d==d1) { d1 giorno 1 ... yyy mese 12 false: d e d1 sono riferimenti a oggetti 2001 anno d=d1; diversi (anche se hanno gli stessi valori per gli attributi) e quindi hanno indirizzi diversi false d == d1 diventa true: l’indirizzo contenuto in d diventa uguale all’indirizzo contenuto in d1 (diventano identici) Java 93 Confronto di uguaglianza – Metodo equals( ) consente di verificare se due oggetti sono uguali (nel senso che hanno lo stesso valore dello stato) • per string: contengono la stessa sequenza di caratteri – Uso: stringa1.equals(stringa2)) String b = new String("Ciao“); String c = new String("Ciao“); if (b.equals(c))…//true – NB: NON usare “==” per verificare uguaglianza di stringhe! • “==” dice solo se stanno nella stessa locazione in memoria • NB: la JVM fa condividere le stesse celle alle costanti di tipo stringa Î per queste “==” funziona – ma per tutte le altre stringhe usare “==” per il confronto è un errore Java 94 Risassumendo: riferimenti e oggetti • Ogni oggetto è dotato di identità univoca – indirizzo (reference) della cella di memoria a cui si trova l’oggetto • Assegnamento (=) assegna il valore del reference • Due oggetti possono essere – uguali: stesso valore dello stato, ma reference diversi • Si verifica con equals – identici: stesso oggetto (stesso reference) • Si verifica con == Java 95 Tipi riferimento per i tipi primitivi • Tipi primitivi comodi, ma a volte si preferirebbe usarli come riferimento, per omogeneità • Java fornisce classi predefinite Integer, Character – Un oggetto Integer contiene un int, ma viene inizializzato solo con i costruttori – Tipo Integer è immutabile – Character Æ classe che contiene char; anche Float, Long, Short, Double (sono in java.lang) Integer i; i = new Integer(5); Integer x = i; int y = x.intValue(); // qui i vale null //i è un rif. a oggetto che contiene 5 // sharing: x e i sono stesso oggetto // per accedere a valore di x NB: Java 1.5 ora fornisce “autoboxing...” Java 96 Packages e Information Hiding Java 97 Packages • Classi (e interfacce, che vedremo tra breve) sono raggruppate in packages • Il package raggruppa (incapsula) classi e interfacce, definendo regole di visibilità • Se visibile nel package A, un'entità X dichiarata nel package B viene denotata come B.X – quindi si possono usare liberamente gli stessi nomi in packages diversi, senza generare confusione Java 98 La struttura di un programma Java • Unità di compilazione (Compilation unit) – Ogni compilation unit è un file che contiene la dichiarazione di una o più classi (o interfacce), delle quali una sola dichiarata pubblica (public class) e avente lo stesso nome del file. – Es. HelloWorld.java corrisponde a classe pubblica HelloWorld. – C’è al più una sola funzione di nome main – Si può specificare il package di appartenenza di ogni classe nella comp.unit (lo stesso per tutte) • se non si specifica, si assume un package senza nome di default • Package – Ogni package è una directory che contiene una o più compilation unit – Il package introduce un nuovo ambito di visibilità dei nomi: unit con lo stesso nome possono stare in package diversi (corrisponde grossomodo a una libreria...) – Un package contiene un insieme di classi pubbliche ed un insieme di classi private al package (“friendly”) – Solo le classi pubbliche si possono importare in altri package Java 99 Information hiding in Java (1) • Classi possono essere: – public • sono visibili a tutti con import • file deve avere stesso nome • al più una public class per ogni file – "friendly": • sono visibili solo all’interno dello stesso package/compilation unit • possono stare in file con altre classi Java 100 Esempio package myTools.text; public class TextComponent { . . .Zzz z;… } package myFigs.planar; public class DrawableElement { ... } class xxx { ... } package myTools.text; public class yyy { ... } class Zzz{ ... } compilation units Java 101 Information hiding (2) • Attributi e metodi di una classe possono essere: – public • sono visibili a tutti quelli per cui la classe è visibile – private • sono visibili solo ai metodi all’interno della stessa classe – "friendly“ (quando non si indica nulla): • sono visibili solo nelle classi all’interno dello stesso package/compilation unit: per gli altri package, è come se fossero private Java 102 Information hiding (3) • Attenzione: quando si dichiara public un attributo o un metodo di una public class, si fa una promessa, agli utilizzatori della classe, che l’attributo o il metodo sarà disponibile e non cambierà, perlomeno dal punto di vista degli utilizzatori della classe. • La promessa è molto vincolante, quindi va fatta con cautela! Meglio promettere poco! • Tutti i metodi e attributi per cui ci si vuole garantire la possibilità di modifica o eliminazione devono essere private (o al massimo, ma solo se indispensabile, friendly). – è meglio private per gli attributi e per tutti i metodi “helper” di una classe, per garantire che non siano usati accidentalmente nel package, obbligandoci a non poterli più togliere o cambiare – se attributo è friendly e qualcuno lo usa non possiamo più cambiarlo! Java 103 Information hiding (4) • E' fortemente consigliato che gli attributi di una classe public siano private o friendly (usare metodi per accedervi!). • Invece i metodi che possono essere usati dagli utilizzatori "esterni" della classe dovrebbero essere public. • Poi vedremo protected… – attributi friendly sono usati solo quando classi all'interno dello stesso package devono avere accesso privilegiato. • p. es. classe Lista deve usare una classe Nodo che implementa i nodi della Lista: è utile che Lista possa accedere ai campi di Nodo, ma gli utilizzatori di Lista non devono potere accedere a Nodo • Questo è un aspetto fondamentale di OO! Java 104 Il package java.lang • Il package java.lang contiene classi di uso molto frequente (String, Object, ecc.) • Non è necessario importare le classi appartenenti al package java.lang prima di utilizzarle Java 105 Ereditarietà • E’ possibile stabilire una relazione “sottoclasse_di (⊆) fra le classi di un programma Java – relaz. d’ordine parziale (riflessiva e transitiva) B public class D extends B {…} D • B classe base, o antenato, o padre, o sovraclasse, sopraclasse, superclasse … • D classe derivata, o discendente, o figlio, o erede, o sottoclasse, … Java 106 La relazione di ereditarietà • La sottoclasse eredita tutta l’implementazione (attributi e metodi) della sopraclasse – Gli attributi e metodi della sopraclasse sono implicitamente definiti anche nella sottoclasse (ma con alcune differenze che vedremo fra poco…) • Una sottoclasse può aggiungere nuovi attributi e metodi ma anche.... • ... ridefinire i metodi delle sue sopraclassi (lasciando invariato num. e tipo dei parametri) (overriding) Java 107 Un semplice esempio public class Automobile { private String modello; private boolean accesa; public Automobile(String modello) { this.modello=modello; this.accesa = false; } public void accendi() {accesa=true;} public boolean puoPartire() {return accesa;} } public class AutomobileElettrica extends Automobile { private boolean batterieCariche; public void ricarica() {batterieCariche=true;} ... } Java 108 Overriding • Una sottoclasse può ridefinire l’implementazione di un metodo (ma in tal caso la segnatura del metodo non deve cambiare) – NB: la segnatura NON include il tipo restituito, che quindi può cambiare secondo le regole della covarianza (vedremo più avanti…) – Esempio: public class AutomobileElettrica extends Automobile { ... public void accendi() {// OVERRIDING ….accensione auto elettrica e’ diversa da quella di auto a benzina… la reimplementiamo Java 109 Gerarchia a più livelli Figure perimeter area ClosedFigure number_of_sides pixel width color scale rotate draw OpenFigure Ellipse Polygon Rectangle Java 110 Accesso ai membri private • Le sottoclassi non possono accedere agli attributi (e metodi) private delle sopraclassi! • è sbagliato scrivere: public void accendi() { if(batterieCariche) accesa=true; ...;} perché accesa è private nella sovraclasse! Java 111 Overriding e la pseudo variabile super • All’interno (e solo all’interno) di un metodo della sottoclasse ci si può riferire ai metodi della sopraclasse tramite la notazione: super.<nome metodo>(<lista par. attuali>) – Esempio: public class AutomobileElettrica extends Automobile { ... public void accendi() {// OVERRIDING if(batterieCariche) super.accendi(); else System.out.println("Batterie scariche");} } Java 112 Accesso ed ereditarietà • Attributi e metodi di una classe possono essere: – public • sono visibili a tutti • vengono ereditati – private • sono visibili solo all’interno della stessa classe • non sono visibili nelle sottoclassi, ma sono ereditati – protected (vedremo meglio) • sono visibili solo alle sottoclassi e alle classi nello stesso package • vengono ereditati – “friendly”: • sono visibili a tutte le classi nello stesso package • vengono ereditati, ma sono visibili alle sottoclassi che stanno nella stesso package della classe Java 113 I costruttori non vengono ereditati • I costruttori non sono ereditati perche’ occorre inizializzare anche i nuovi attributi (costruttore della superclasse non li inizializza) • Per inizializzare gli attributi private ereditati, all’interno di un costruttore è possibile richiamare il costruttore della sopraclasse tramite la notazione: super(<lista di par. attuali>) posta come prima istruzione del costruttore Se il programmatore non chiama esplicitamente un costruttore della sopraclasse, il compilatore inserisce automaticamente il codice che invoca il costruttore di default della sopraclasse (che potrebbe non esistere!) – In AutomobileElettrica: public AutomobileElettrica(String modello) { super(modello); //qui inizializza modello e accesa batterieCariche=false; } Java 114 La classe Object • In mancanza di una differente indicazione, una classe Java estende la classe Object • La classe Object fornisce alcuni metodi tra i quali vale la pena citare i seguenti: public boolean equals(Object); public String toString(); public Object clone(); Java 115 Equals • Dice se due oggetti sono equivalenti – Che cosa ciò esattamente significhi dipende dal tipo dell'oggetto • per esempio, due set sono equivalenti se contengono gli stessi elementi, indipendentemente dall'ordine di inserimento • va pertanto spesso ridefinita • L'implementazione fornita da Object coincide con "==" – riferimenti uguali Java 116 toString public String toString ( ) • Restituisce una rappresentazione testuale dell’oggetto • Implementazione fornita da Object: – una stringa che mostra il nome del tipo seguito da una rappresentazione testuale del valore dell’oggetto (tipicamente, nomi e valori dei campi racchiusi tra parentesi quadre) Java 117 clone() • Il metodo clone restituisce una copia dell'oggetto, nello stesso stato – copia di tutti i campi – (se si vuole che classe fornisca clone, occorre scrivere: implements Cloneable, altrimenti chiamata a clone() fornita da Object provoca eccezione) • Cloneable è un’interfaccia: vedremo poi… • Eccezioni: vedremo più avanti… – Se si vuole fare una copia "completa" dell'oggetto, il metodo clone va ridefinito • la clone fornita da Object fa una "copia superficiale" • se un attributo è un riferimento, viene copiato il riferimento! Java 118 Costruire software estendibile usando ereditarietà Java 119 Esempio: Progetto tradizionale in C typedef struct …Figura; Figura Cerchio Rettangolo Figura figure[100]; Triangolo void disegnaTutto(Figura *figure) { for (i= 0; i<100;i++) { if (figure[i] è “rettangolo”) “disegna rettangolo” if (figure[i] è “triangolo”) “disegna triangolo” if (figure[i] è “cerchio”) “disegna cerchio” } } figure[1] = “rettangolo”; figure[2] = “triangolo”; figure[3] = “cerchio”; Estendere progetto tradizionale: arriva il Trapezio Figura typedef struct …Figura; Figura figure[100]; Cerchio Rettangolo Triangolo Trapezio void disegnaTutto(Figura *figure) { for (i= 0; i<100;i++) { if (figure[i] è “rettangolo”) “disegna rettangolo” if (figure[i] è “triangolo”) “disegna triangolo” if (figure[i] è “cerchio”) “disegna cerchio” if (figure[i] è “trapezio”) “disegna trapezio }} figure[1] = “rettangolo”; figure[2] = “triangolo”; figure[3] = “cerchio”; figure[4] = “trapezio” Codice già definito per disegnaTutto deve cambiare per potere supportare il nuovo tipo!!! Es. in versione OO //si definisce classe (astratta) Figura, con metodo (astratto) disegna(), e sue eredi Rettangolo, Cerchio, Triangolo che implementano disegna() class Figura public void disegna(){…} class Cerchio public void disegna(){…} classRettangolo public void disegna(){…} Figura figure = new Figura[100]; class Triangolo public void disegna(){…} figure[1] = new Rettangolo(); figure[2] = new Triangolo(); figure[3] = new Cerchio(); … ….disegnaTutto(figure); public static void disegnaTutto(Figura [] figure) { for (i= 0; i<100;i++) figure[i].disegna();} legale in Java: a run-time si decide quale impl. di disegna() chiamare: quella di Cerchio, di Rettangolo, o di Triangolo… Quantità di codice è più o meno la stessa di prima, ma è meglio organizzato... Estendibilità: arriva il Trapezio Figura figure = new Figura[100]; class Figura public void disegna(){…} class Cerchio public void disegna(){…} figure[1] = new Rettangolo(); figure[2] = new Triangolo(); figure[3] = new Cerchio(); figure[4] = new Trapezio(); classRettangolo public void disegna(){…} class Triangolo class Trapezio public void public void disegna(){…} disegna(){…} …disegnaTutto(figure); public static void disegnaTutto(Figura [] figure) { for (i= 0; i<100;i++) figure[i].disegna(); } …ma occorre capire perche’ si può fare cosi’!!! Codice definito per Figura non cambia!!! Polimorfismo “Chiave” per costruire software estendile Java 124 Esempio: codice definito per sopraclasse funziona anche con sottoclasse public class usaAutomobile { public static int partenza(Automobile p) { if (p.puoPartire()) p.accendi(); …} public static void main(String args[]) { Automobile myCar = new AutomobileElettrica(“T”);\\ legale!! partenza(myCar); //funziona anche con AutomobileElettrica .... Vediamo perché tutto funziona come ci aspettiamo…. Java 125 Polimorfismo • L'esempio precedente è un caso di polimorfismo • Polimorfismo è la capacità per un elemento sintattico di riferirsi a elementi di diverso tipo • In Java una variabile di un tipo riferimento T può riferirsi ad un qualsiasi oggetto il cui tipo sia T o un sottotipo di T • Similmente un parametro formale di un tipo riferimento T può riferirsi a parametri attuali il cui tipo sia T o un sottotipo di T Java 126 Tipo apparente e tipo effettivo • Oggetti hanno un tipo apparente (“statico”): quello definito dalla dichiarazione – Es. Automobile myCar; • Oggetti hanno anche un tipo effettivo (“dinamico”): quello del costruttore usato a run-time per definirlo • Es. AutomobileElettrica yourCar = new AutomobileElettrica(); AutomobileElettrica() restituisce un oggetto di tipo effettivo AutomobileElettrica • In Java, tipo effettivo può essere sottotipo del tipo apparente Automobile myCar = new AutomobileElettrica(); Java 127 Esempio class AutomobileElettrica extends Automobile { boolean batterieCariche; void ricarica() {batterieCariche=true;} void accendi() { if(batterieCariche) accesa=true; else accesa=false; } } Automobile myCar = new Automobile(); AutomobileElettrica yourCar = new AutomobileElettrica(); . . . myCar = yourCar; il tipo statico di myCar è Automobile quello dinamico è AutomobileElettrica Java 128 Regola di assegnamento polimorfico A un oggetto di tipo apparente T si può sempre assegnare un oggetto il cui tipo apparente S è sottotipo di T (ma non viceversa) Questo consente che il tipo effettivo possa essere sottotipo di quello apparente Automobile myCar = new AutomobileElettrica(); • Il compilatore quindi verifica che ogni oggetto venga manipolato correttamente solo in base al tipo apparente • La regola precedente però garantisce che a run time non sorgano errori se invece si opera su un oggetto il cui tipo dinamico è un sottotipo del tipo statico Java 129 Uso delle classi dell’Esempio: assegnamento Corretto: tipo (apparente) di mycar è sopratipo di tipo (apparente) di yourCar myCar xxx Ford false modello accesa Automobile myCar = new Automobile(“Ford”); AutomobileElettrica yourCar = new AutomobileElettrica(“El”); ... myCar = yourCar; yourCar xxx El false Java true modello accesa 130 batterieCariche Uso delle classi dell’Esempio: assegnamento scorretto: tipo (apparente) di yourCar non è sopratipo di tipo (apparente) di myCar Automobile myCar = new Automobile(); AutomobileElettrica yourCar; ... yourCar = myCar; Se questo assegnamento fosse legale, il tipo effettivo potrebbe non essere sottotipo del tipo apparente: non usabile (p.es. senza alcuni metodi pubblici, ecc) Java 131 Uso delle classi dell’Esempio: assegnamento Corretto: tipo (apparente) di mycar è sopratipo del tipo effettivo Automobile myCar = new AutomobileElettrica(); Java 132 Uso delle classi dell’Esempio: assegnamento Scorretto: tipo (apparente) di yourCar non è sopratipo del tipo del costruttore AutomobileElettrica yourCar = new Automobile (); Java 133 Uso delle classi dell’Esempio: chiamata di un metodo Automobile myCar = new AutomobileElettrica(); ... myCar.puoPartire(); //ok, chiama metodo di Automobile myCar.accendi(); //ok, chiama metodo di AutomobileElettrica myCar.ricarica(); //KO, ricarica non è metodo di Automobile AutomobileElettrica yourCar = new AutomobileElettrica (); yourCar.ricarica(); //OK, chiama metodo di AutomobileElettrica Java 134 Polimorfismo e binding dinamico • In Java, a fronte della invocazione x.f(x1,...,xn), l’implementazione scelta per il metodo f dipende dal tipo dinamico di x e non dal suo tipo statico • Il legame (binding) tra il metodo invocato e il metodo attivato è dinamico, dipende dal tipo attuale dell'oggetto. Dispatching è la tecnica usata per realizzare binding dinamico. Java 135 Esempio public class usaAutomobile { public static void main(String args[]) { Automobile a1 = new Automobile(“Ford”); Automobile a2 = new AutomobileElettrica(“T”); a1.accendi(); a run time chiama implementazione di Automobile a2.accendi(); a run time si chiama implementazione di AutomobileElettrica partenza(a2); solo a run time si può conoscere il tipo effettivo public static int partenza(Automobile a) { a.accendi();//funziona anche con AutomobileElettrica …} Java 136 Dispatching (binding dinamico) • Esempio: static void partenza (Automobile p) { //accende p e la fa partire p.accendi(); ... • Il compilatore non ha modo di stabilire quale implementazione del metodo accendi() deve chiamare: quella di Automobile o quella di AutomobileElettrica? Il tipo effettivo del parametro p è noto solo quando accendi viene chiamato, cioè a run-time • Compilatore allora non genera il codice per eseguire il metodo, ma genera codice che cerca qual’è l’implementazione giusta di accendi in base al tipo effettivo di p e la esegue: DISPATCHING – varie tecniche implementative: per es. ogni oggetto può contenere un puntatore al codice dei propri metodi Java 137 Implementazione dispatching accendi puntatore al codice a1 a2 accendi puntatore al codice Java 138 Overloading e overriding • Binding dinamico non va confuso con overloading class Punto2D{ public float distanza(Punto2D p){…} } class Punto3D extends Punto2D { public float distanza(Punto3D p){…}//OVERLOADING!!! } • Il metodo distanza di Punto3D ha una segnatura diversa da quella di distanza dichiarato in Punto2D: NON è overriding Î non si applica binding dinamico • Punto2D p = new Punto3D(); • p.distanza(p); //chiama Punto2D.distanza(Punto2D)!!! Java 139 Regola per chiamata metodi • il compilatore Java, quando trova una chiamata di un metodo: x.m(p); risolve staticamente overloading, individuando la segnatura del metodo chiamato in base al tipo statico X di x e al tipo statico P del parametro attuale p • Risultato è: X.m(P): in base a regole di overloading viene trovato un metodo di X (o ereditato da X) con parametro il cui tipo è un sopratipo di P. Risultato Y.m(P’) con Y e P’ in generale sopratipi di X e P. • Binding dinamico si applica a run-time: il codice sceglie a run time il metodo “più vicino” fra i metodi che hanno il prototipo Y.m(P’) stabilito staticamente. Java 140 Esempio class Punto2D{public float distanza(Punto2D…){…}…} class Punto3D extends Punto2D {public float distanza(Punto3D …){…}} //NB distanza overloaded Punto2D p1,p2; Punto3D p3; p1 = new Punto2D(3,7); p2 = new Punto3D(3,7, 4); System.out.println(p1.distanza(p2)); System.out.println(p2.distanza(p1)); p3 = new Punto3D(6,7, 5); System.out.println(p2.distanza(p3)); System.out.println(p3.distanza(p1)); System.out.println(p1.distanza(p3)); Punto3D p4 = new Punto3D(6,1, 5); System.out.println(p3.distanza(p4)); metodo di Punto2D Punto2D Punto2D tipo statico di p2 è Punto2D Punto2D tipo statico di p3 è Punto3D e tipo statico di p1 è Punto2D Î scelto distanza(Punto2D) Punto2D tipo statico di p1 è Punto2D Î scelto distanza(Punto2D) tipo statico di p3 è Punto3D e tipo statico di p4 è Punto3D Î scelto distanza(Punto3D) Java 141 Classi e metodi astratti • Un metodo astratto è un metodo per il quale non viene specificata alcuna implementazione • Una classe è astratta se contiene almeno un metodo astratto • Non è possibile creare istanze di una classe astratta • Le classi astratte sono molto utili per introdurre delle astrazioni di alto livello Java 142 Classi e metodi astratti: esempio abstract class Shape { static Screen screen; Shape(Screen s) {screen=s;} abstract void show(); } class Circle extends Shape { void show() { ... //implementazione del metodo } } ... Shape s=new Shape(); // errato Circle c=new Circle(); // corretto Java 143 Classi e metodi final • Se vogliamo impedire la creazione di sottoclassi di una certa classe la definiremo final • Esempio: final class C {...} class C1 extends C // errato • Similmente, se vogliamo impedire l’overriding di un metodo dobbiamo definirlo final • Esempio: class C { final void f() {...} } class C1 extends C { void f() {...} // errato } Java 144 I limiti dell’ereditarietà semplice • L’ereditarietà semplice non permette la descrizione di numerose situazioni reali • Esempio: – Supponiamo di avere una classe Giocattolo ed una classe Automobile. In assenza di ereditarietà multipla non posso definire la classe AutomobileGiocattolo Java 145 La soluzione di Java • Distingue tra una gerarchia di ereditarietà (semplice) ed una gerarchia di implementazione (multipla) … • … introducendo il costrutto interface Java 146 Interfacce • Una interfaccia è come una classe che può avere solo attributi costanti e i cui metodi sono tutti pubblici ed astratti • Sintassi: interface <nome> { <lista di definizione di attributi costanti e metodi privi di corpo> } Java 147 Attributi costanti • Implicitamente, gli attributi dichiarati in una interfaccia sono – visibili alla classe che la implementa – immodificabili (static final) interface scalable { int SMALL=0,MEDIUM=1,BIG=2; //static e final void setScale(int SIZE); } Java 148 Interfacce ed ereditarietà • Una interfaccia può ereditare da una o più interfacce • Sintassi: interface <nome> extends <nome1>,..,<nomen> { ... } Java 149 La gerarchia di implementazione • Una classe può implementare una o più interfacce, ma estendere al più una classe – se la classe non è astratta deve fornire una implementazione per tutti i metodi presenti nelle interfacce che implementa – altrimenti la classe è astratta • Sintassi: class <nome> extends <nome0> implements <nome1>,..,<nomen> {...} Java 150 Classi e tipi Java 151 Possibili errori di tipo in un linguaggio • Assegnamenti tra oggetti di tipo incompatibile • Chiamate di metodi incompatibili (numero e tipo dei parametri) Java 152 Tipizzazione forte • Se il compilatore può garantire che NON sorgano errori di tipo durante l'esecuzione si dice che il linguaggio è sicuro riguardo ai tipi ("type safe") • Quando ciò accade si dice che il linguaggio è fortemente tipizzato ("strongly typed") • Java è fortemente tipizzato Java 153 Ancora sui tipi in Java • Rispetto a C, C++: – viene fatta automatica garbage collection • oggetti accessibili via riferimento sono automaticamente deallocati quando non più riferiti ciò evita il pericolo di generare dangling references (riferimenti ad aree di memoria deallocata) – viene fatto controllo automatico a run time che gli indici di un array non escano dai limiti Java 154 Gerarchia di tipi • Una classe definisce un tipo • Una sottoclasse (transitivamente) definisce un sottotipo • Un oggetto del sottotipo è sostituibile a un oggetto del tipo • Si distingue tra Java garantisce che ciò non comprometta la type safety (vedremo come) – tipo statico • il tipo dichiarato – tipo dinamico (o attuale) • il tipo dell'oggetto attualmente assegnato Java 155 Tipo statico e dinamico • Il compilatore verifica che ogni oggetto venga manipolato correttamente in base al tipo statico • Il linguaggio garantisce che a run time non sorgono errori se invece si opera su un oggetto il cui tipo dinamico è un sottotipo del tipo statico Java 156 Conversioni automatiche di tipo Java 157 Promozioni byte short int long -> -> -> -> short, int, long, float, double int, long, float, double long, float, double float or double float char -> -> double int, long, float, double Java 158 Conversioni forzate: casting •È possibile forzare una conversione di tipo attraverso l’operatore di casting: (<tipo>)<espressione> •Tra tipi primitivi sono consentite le seguenti conversioni forzate (quando possibile e con perdita di informazione) short char int long float double byte -> -> -> -> -> -> -> byte, byte, byte, byte, byte, byte, char char short short, short, short, short, Java char char, int char, int, long char, int, long, float 159 Casting in generale • È possibile forzare esplicitamente la conversione da un tipo riferimento T ad un sottotipo T1 purché: – Il tipo dinamico dell’espressione che convertiamo sia un sottotipo di T1 • Esempio: Animale a = . . .; Gatto mao = . . .; a = mao; // OK, assegnazione polimorfica mao = (Gatto)a; // corretto (casting) perché a è un gatto //se non fosse un Gatto Æ eccezione (vedremo) Java 160 instanceof • Per evitare errori runtime e stabilire qual’è il tipo effettivo dell’oggetto estratto con downcasting si può usare operatore instanceof Object a; int i = System.in.read(); if (i>0) a = new String(); else a = new Integer(); if (a instanceof String) return a.equals(“abcd”); … Java 161 Esempio: instanceof e ridefinizione di equals class Data { private int giorno; private int mese; private int anno; ... public int leggi_giorno(){ ...} public boolean equals(Object o) { if (!(o instanceof Data) return false; Data d= (Data) o; return (giorno==d.giorno && mese == d.mese && anno == d.anno) } … Java 162 ArrayList • Array in Java (come in C) hanno dimensione fissa (anche se sono oggetti allocati dinamicamente) – Se a un cero punto un array non basta più a contenere gli oggetti, non si può “allungarlo”: occorre (deallocarlo e) allocarne uno nuovo più capiente: SCOMODO • ArrayList sono contenitori “estendibili” e “accorciabili” dinamicamente – Prima di JDK5 potevano contenere solo oggetti Object – In JDK5 sono parametrici (generici) rispetto al tipo degli oggetti contenuti – (Prima ancora c’erano i Vector, analoghi ad ArrayList non generici) ArrayList <Person> team = new ArrayList<Person>(); – crea ArrayList privo di elementi e anche di “posizioni” per memorizzarli • Un ArrayList può essere esteso col metodo add che aggiunge elementi in fondo, oltre a quelli presenti team.add(new Person(“Bob”,...)); team.add(new Person(“Joe”,...)); – ora team.size() vale 2 Java 163 • Per gli arrayList non c’è la comoda notazione a[i] utilizzabile per gli array, occorre usare metodi get e set, che (al solito) fanno riferimento a indici che iniziano da 0 team.get(1); //dà la Person di nome Joe team.set(0, new Person(“Mary”,...)); //sostituisce Mary a Bob • NB ArrayList sono implementati come array di puntatori => metodo add allunga di 1 la (parte utilizzata del) l’array – Metodi add(indice, oggetto) e remove(indice) aggiungono e tolgono un elemento nella posizione indicata, alterando la lunghezza dell’ArrayList team.add(1, new Person(“Sue”,...)); //ora ci sono Mary, Sue, Joe team.remove(0); // rimuove Mary, ora ci sono Sue e Joe – NB add e remove comportano “traslazione” di segmenti dell’ArrayList: sono operazioni “costose” (costo lineare...) – ATTENZIONE: metodo set NON deve essere usato per inserire un elemento in una posizione che non c’è team.set(4, new Person(“Jack”,...)); // scorretto ma solo per sostituire un oggetto già presente in quella posizione Java 164 • ArrayList sono contenitori => si possono scandire gli elementi usando ciclo for-each for(Person p: team){ //fa qualcosa con la persona p } è equivalente al tradizionale for for(int i=0; i<team.size(); i++){ Person p = team.get(i); //fa qualcosa con la persona p } Java 165 ArrayList, wrappers e autoboxing • Gli arraylist (come tutti gli altri contenitori) possono contenere solo oggetti, NON valori di tipi primitivi – per memorizzare valori primitivi bisogna includerli in oggetti delle classi wrapper ArrayList<Integer> intList = newArrayList<Integer>(); intList.add(7); – viene accettato perchè tradotto (dal compilatore) in intList.add(new Integer(7)); questo si chiama AUTOBOXING – Simmetricamente si può scrivere int n = intList.get(3); perchè il metodo per estrarre il valore primitivo viene aggiunto dal compilatore che produce int n = intList.get(3).intValue(); Java 166 Vector (Java<1.5) • Coi Vector (nelle versioni precedenti di Java) si possono memorizzare solo oggetti Object, quindi in aggiunta occorre anche un casting prima di effettuare un “unwrap” Vector myVector = new Vector (); myVector.add(new Integer(5)); int n = ((Integer)myVector.get(0)).intValue(); Java 167 I/O formattato • JDK 5.0 offre I/O formattato ispirato a C • Per input: costruire uno scanner collegato allo “standar Input Stream” System.in, poi usare metodi della classe Scanner (nextLine() legge la prossima riga, next() la prossima stringa fino a uno spazio, nextInt() il prossimo int, nextDouble()…, hasNext() dice se c’è una stringa in input, hasNextInt()…, nextFloat()…, nextBoolean()…) • Per output: System.out fornisce il “buon vecchio” printf di C, con le usuali convenzioni di formattazione, più print e println per stringhe import java.util.*; ... Scanner in = new Scanner(System.in); System.out.println(“Come ti chiami?”); String nome = in.nextLine(); System.out.println(“Quanti anni hai?”); int eta = in.nextInt(); System.out.println(“Ciao ”+nome+” tra un anno avrai ”+(eta+1)+” anni”); /*oppure: System.out.printf(“Ciao %s tra un anno avrai %d anni”, nome, eta+1); */ ... Java 168 Enumerazioni • In JDK5.0 si possono dichiarare tipi enumerati, per modellare insiemi con ridotta cardinalità enum Size {SMALL, MEDIUM, LARGE, EXTRA_LARGE}; Size s = Size.MEDIUM; • Size è una vera classe: ha esattamente quattro istanze, non se ne possono costruire altre – (Non c’è bisogno di usare equals per confrontare i valori, basta ==) • • s puo`essere solo null o uno dei valori enumerati A una classe enumerata si possono aggiungere costruttore, metodi, attributi: permettono di associare qualsiasi informazione alle costanti enumerate – Costruttori invocati solo quando vengono costruite le costanti enum Size {SMALL(“S”), MEDIUM(“M”), LARGE(“L”), EXTRA_LARGE(“XL”) private String abbreviation; private Size(String abbreviation) { //il costruttore this.abbreviation=abbreviation } public String getAbbreviation(){return abbreviation;} }; Java 169 • Tutte classi enumerate eredi della classe Enum che offre i seguenti metodi // dà la costante enumerata della classe indicata che ha quel nome static Enum valueOf(Class enumClass, String name) // dà il nome della costante enumerata String toString() – Molto utili nell operazioni di I/O di valori della classe enumerata Scanner in = new Scanner(System,in); String str = in.next(); //e.g. se letta stringa “SMALL”... Size siz = Enum.valueOf(Size, str); //...dà costante Size.SMALL System.out.println(Size.LARGE.toString()); //stampa “LARGE” • Ogni classe enumerata ha un metodo static che restituisce un array contenente tutti i valori della classe Size[] valori = Size.values(); Java 170 • Un esempio più ricco: pianeti del sistema solare, associati alla propria massa e raggio; si può calcolare il peso di un oggetto su ogni pianeta public enum Planet { MERCURY (3.303e+23, 2.4397e6), VENUS (4.869e+24, 6.0518e6), EARTH (5.976e+24, 6.37814e6), MARS (6.421e+23, 3.3972e6), JUPITER (1.9e+27, 7.1492e7), SATURN (5.688e+26, 6.0268e7), URANUS (8.686e+25, 2.5559e7), NEPTUNE (1.024e+26, 2.4746e7), PLUTO (1.27e+22, 1.137e6); private final double mass; // in kilograms private final double radius; // in meters Planet(double mass, double radius) { this.mass = mass; this.radius = radius; } public double mass() { return mass; } public double radius() { return radius; } // universal gravitational constant (m3 kg-1 s-2) public static final double G = 6.67300E-11; public double surfaceGravity() { return G * mass / (radius * radius); } public double surfaceWeight(double otherMass) { return otherMass * surfaceGravity(); } } Java 171 • A partire dal peso di un corpo sulla terra, calcola e stampa il peso su tutti gli altri pianeti public static void main(String[] args) { double earthWeight = Double.parseDouble(args[0]); double mass = earthWeight/EARTH.surfaceGravity(); for (Planet p : Planet.values()) System.out.printf("Your weight on %s is %f%n", p, p.surfaceWeight(mass)); } $ java Planet 175 Your weight on MERCURY is 66.107583 Your weight on VENUS is 158.374842 Your weight on EARTH is 175.000000 Your weight on MARS is 66.279007 Your weight on JUPITER is 442.847567 Your weight on SATURN is 186.552719 Your weight on URANUS is 158.397260 Your weight on NEPTUNE is 199.207413 Your weight on PLUTO is 11.703031 Java 172