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 Il linguaggio Java e la progettazione di software object-oriented Java Codice C di Alberto 1 •La manipolazione dei dati avviene con accesso diretto alla rappresentazione del tipo: 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 2 Richiami sui tipi di dati Codice C di Bruno typedef struct { int giorno; int mese; int anno; } Data; Java 3 • Tipo = insieme di valori + insieme di operazioni • Es. int: – valori: …, -2, -1, 0, 1, 2, … – operazioni:: +,-,*,/, <, == … – le operazioni si applicano a entità (oggetti) di tipo int. • Tipi built-in (primitivi in Java): int, float, char, double,.. – Per i tipi primitivi, il dominio e le operazioni possibili sono definite dal linguaggio • Tipi definiti dall’utente – Costruttori di tipo: definiscono nuovi tipi composti a partire da altri tipi (array, struct, ...) – Operazioni predefinite anche per i costruttori di tipo (a.y, b[i], ecc.) – Strutture dati complesse possono essere costruite partendo dai tipi semplici tramite i costruttori di tipo. Java 4 Vantaggi del concetto di tipo La definizione di Data cambia! • Dopo avere rilasciato una prima versione, Alberto modifica la rappresentazione per semplificare “stampaData” • Classificare i dati: – Programmatore non deve occuparsi di celle di memoria (astrazione sulla struttura di rappresentazione) typedef struct { int giorno; char mese[4]; int anno; } Data ; void stampaData(Data d) { printf("%d", d.giorno); • Protezione da operazioni illegali (type checking) Data d; int g; d = g; // illegale! …forse non si voleva scrivere così!*/ – Errori di tipo sono rilevati dal compilatore: non è necessario eseguire il programma per scoprirli printf("%d", d.mese); printf("%d", d.anno); } Java 5 Disastro! 6 Le modifiche sono costose • Il codice scritto da Bruno non funziona più! • Occorre modificarlo: • 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 Data d; … if (d.giorno == 27) paga_stipendi(); if (strcmp(d.mese,"Dic")) paga_tredicesime() … Java Java • La realizzazione interna (implementazione) delle strutture dati è una delle parti più soggette a cambiamenti (per maggiore efficienza, semplificazione codice...)! 7 Java 8 Soluzione Come usare una Data? • Il problema può essere risolto disciplinando o impedendo l'accesso alla realizzazione della struttura dati. • Se si impedisce a Bruno l'accesso ai campi della struttura Data, come può Bruno utilizzare le date di Alberto? – Bruno non deve più scrivere: (d.mese == 12) 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 9 Funzioni predefinite per Data Java 10 Uso delle operazioni predefinite • L’accesso alla struttura dati è fornito unicamente tramite operazioni predefinite • Il codice di Bruno è scritto in modo tale da usare solo le operazioni predefinite: In C: Data d; … inizializzaData(*d, 14,12,2000); if (leggi_giorno(d) == 27) paga_stipendi(); if (leggi_mese(d) == 12) paga_tredicesime() … 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 11 Java 12 Modifica struttura dati Incapsulamento • 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"; … } • Abbiamo INCAPSULATO la struttura dati! • Ogni modifica alla struttura resta confinata alla struttura stessa • Le modifiche sono – più semplici – più veloci – meno costose • Ma il codice di Bruno non cambia! Java 13 Tipo di dato astratto (ADT) Java 14 Progettare l'interfaccia • 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 • 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: – 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 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 15 Java 16 ADT in C? Costrutti per ADT • 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. • 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. • C inoltre manca di molte altre astrazioni utili per programmazione inthe-large, che si ritrovano invece nei linguaggi orientati agli oggetti Java 17 Java Il linguaggio Java 18 Caratteristiche • Object-oriented (OO) • Distribuito • Definito dalla Sun Microsystems nel 1995/96. Useremo la versione J2SE 1.5 (Java 2 Standard Edition) • Esistono anche J2EE (Java 2 Enterprise Edition) – RMI • Indipendente dalla piattaforma – Bytecode e Java Virtual Machine – costruita sopra J2SE, offre servlets e JSP, EJB… – per sviluppare applicazioni "server side" • Sicuro – Esecuzione in una "sandbox" che garantisce che non si possa danneggiare l'host • J2ME (Java 2 Micro Edition) – per piccoli dispositivi (telefoni, PDA,…) Java 19 Java 20 Programmazione “in-the-small” e programmazione “in-the-large” Quadro d'insieme • Programmazione “in the small” – Tipi primitivi (simili al C, con differenze) – Dichiarazione di variabili – Strutture di controllo: selezione condizionale, cicli • 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 • sono identiche a quelle del C: if, switch, while, for... • 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 21 – Array (differenze col C) • Programmazione “in the large” – Classi – Interfacce – Packages – Information Hiding – Ereditarietà – Polimorfismo e binding dinamico Java 22 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 Il concetto di classe ADT in Java 23 Java 24 Il primo programma Java 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 file: HelloWorld.java public class HelloWorld { public static void main(String args[]){ System.out.println(“Hello world!”); } } 25 Esempio di classe in Java Java 26 Il costrutto class 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*); } • 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 • class è come struct, ma alcuni campi possono essere funzioni. Dati e funzioni sono incapsulati. I private sono “invisibili all’esterno”, mentre i “public” no. • Definisce proprio un ADT. • Una nuova classe viene definita nel seguente modo: <visibilità> class <nome classe> { <lista di definizioni di attributi, costruttori e metodi> Java 27 Java 28 Attributi di una classe Altro esempio • La struttura dati di una classe è definita dai suoi attributi • Esempio: ATTRIBUTI class Data { private int giorno; private int mese; private int anno; ... – giorno, mese, anno sono gli attributi di Data } Java class Automobile { private String colore, marca, modello; private int cilindrata, numPorte; private boolean accesa; } 29 Java 30 Oggetti Classi e oggetti • In un programma OO si dichiarano classi per potere definire degli oggetti • Un oggetto è un’area di memoria che contiene valori per ogni attributo definito nella classe (e’ quindi come un valore di una struct) • 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 e associare gli oggetti alle variabili • (oggetti e variabili non sono la stessa cosa...) nome dell’oggetto • Tutti gli oggetti della stessa classe hanno la stessa struttura nome della classe di cui l’oggetto è istanza data1 Data – Il numero e tipo dei loro attributi di istanza è lo stesso nome e valore degli attributi – Ad esempio, la classe Data definisce tutte le date possibili, tramite gli attributi giorno, mese, anno. Java attributi 31 giorno = 30 mese = 12 anno = 2001 Java 32 Metodi di una classe Oggetti • Stato di un oggetto: Ogni oggetto, in ogni istante dell’esecuzione del programma, è caratterizzato da uno stato – Lo stato rappresenta la condizione in cui si trova l’oggetto – Lo stato è definito dai valori delle variabili interne all'oggetto (attributi) – Es. lo stato dell’oggetto data1 di tipo Data è definito dal valore degli attributi giorno, mese, anno • I metodi sono procedure che • Comportamento di un oggetto – Determina come agisce e reagisce un oggetto – È definito dall’insieme di operazioni che si possono richiamare sull’oggetto –“appartengono” agli oggetti (se non si usa la parola chiave static—vedremo ) –hanno come parametro implicito l’oggetto cui appartengono (this) • Un oggetto è pertanto un’entità “viva”, che può essere e modificata e letta tramite operazioni Java 33 Definizione di metodi Java 34 Accesso ad attributi e metodi • I metodi definiscono il comportamento degli oggetti appartenenti ad una classe • Ogni metodo è definito come segue: • Tramite la "notazione punto" • Esempio: Data d; int x; ...//codice che inizializza d x = d.leggi_giorno(); <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 • 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(){...}; ... } M E T O D I • Un oggetto è detto istanza (o esemplare) della classe che abbiamo usato come stampo per crearlo 35 – 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 36 Qualche cambiamento... Oggetti mutabili (e immutabili) • Cambia la sintassi... • Lo stato degli oggetti (mutabili) può cambiare nel tempo, chiamando metodi opportuni – 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; -- a è una variabile di tipo int d è una variabile di classe Data Data data1.giorno_dopo() giorno = 31 mese = 12 anno = 2001 • Esistono anche oggetti immutabili (cioè che non possono essere modificati dopo la loro creazione), come ad es. la classe predefinita String. 37 Il metodo giorno_dopo() Java 38 Private e Public • 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(){ • Attraverso i metodi "public" di una classe è possibile vedere qual’è lo stato di un oggetto ... – Data d; int x; ... x = d.leggi_giorno(); //@ ensures (*incrementa la data*) giorno++; if (giorno > 31){ giorno = 1; mese++; if (mese > 12) { mese = 1; anno++;}}} • …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. … Data d; … d.giorno_dopo(); /*modifica lo stato dell’oggetto d*/ Java data1 Data giorno = 30 mese = 12 anno = 2001 analogia con le struct del C Java data1 39 Java 40 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 • Il costrutto di classe è caratteristico dei linguaggi orientati agli oggetti (OO) • Caratteristiche principali dei linguaggi OO (ne vedremo altre) Programmazione "in the small" – Incapsulamento delle strutture dati – Protezione, al fine di impedire l'accesso all'implementazione (information hiding) Java 41 Java Variabili e tipi riferimento Tipi primitivi e variabili • Tipi numerici: – – – – – – byte: short: int: long: float: double: 64 bit • 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: 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; – – – – 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... • Altri tipi: – boolean: – char: 42 true false 16 bit, carattere Unicode • 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 43 Java 44 Dichiarazione e inizializzazione 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 – 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 giorno mese anno Java • 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 45 New Java Differenze tra dichiarazione e creazione • 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 Data data; data = new Data( ); data = new Data( ); 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 46 47 Creato Creatocon conlala prima primanew. new. Data Data Java Creato Creatocon conlalaseconda secondanew. new.IlIl riferimento riferimentoalalprimo primooggetto oggetto Data Dataèèperso. perso. Non Nonc’è c’èpiù piùmodo mododidiaccedere accedere all’oggetto. all’oggetto. L’oggetto L’oggettoverrà verràdistrutto distruttodal dal garbage garbagecollector. collector. 48 Creazione e distruzione degli oggetti Costruttori • Se implementazione deve essere private, unico modo per inizializzare un oggetto è specificare uno o più metodi particolari, chiamati costruttori – 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 • La creazione di un oggetto (tramite new) 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 • Un costruttore è un metodo speciale: 49 • Vantaggio: all’allocazione si esegue sempre anche l’inizializzazione, eliminando una frequente fonte di errori. Java 50 Costruttori di default Esempio di costruttore public class Date { • Se non si definisce nessun costruttore, il compilatore fornisce il costruttore di default (senza parametri), che svolge le seguenti funzioni: private int month, day, year; public Date(int d, int m, int y) { // day month year year = y; month = m; day =d) – 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 ... } … Date d = new Date(3,12,1987); • Il costruttore di default (senza parametri) viene fornito dal compilatore a meno che non si definiscano altri costruttori Î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 51 Java 52 I costruttori: esempio public class C { private int i1; private int i2; public C() { i1 = 0; i2 = 0; } Assegnamento • L’operatore di assegnamento “=“, quando applicato a: – Variabili e espressioni di tipi primitivi: copia il valore. – Es. int x,y; x=5; y=x; “omaggio” del compilatore: non è necessario scriverlo • In y viene copiato il valore contenuto in x; } .... public class Esempio { public static void main(String args[]) { C x; Corretto perchè il compilatore ha inserito x = new C(); automaticamente il costruttore di default x = new C(5,7); } } – variabili e espressioni di tipo riferimento: copia il riferimento, non l’oggetto. – Es. Data d1, d2;d1=new Data(1,2,2004); d1 = d2; – In d1 è copiato il riferimento, cioe’ d1 e d2 referenziano (ossia condividono) lo stesso oggetto. Sbagliato, non esiste nessun costruttore che prenda due parametri Java 53 Java Es. di sharing Condivisione (sharing) • Un oggetto è condiviso tra due variabili se entrambe accedono a esso • L'assegnamento di variabili di tipo riferimento genera condivisione (vedi d1 e d2 nell'esempio precedente) • Se oggetti condivisi sono modificabili, le modifiche apportate attraverso una variabile sono visibili anche attraverso l'altra (side effect) • Questo di per se’ non e’ un problema, ma può rendere I programmi difficili da capire. Java 54 55 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 56 Confronto di uguaglianza Reference e operatore “== ” – Metodo equals( ) consente di verificare se due oggetti sono uguali (nel senso che hanno lo stesso valore dello stato) •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; • 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 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 57 Java Esempio: allocazione degli oggetti Stack vs. Heap • Le variabili consentono di accedere agli oggetti • Le variabili sono sempre allocate nello stack a run-time – allocate quando si chiama il sottoprogramma in cui sono dichiarate – deallocate quando il sottoprogramma ritorna al chiamante • Gli oggetti referenziati dalle variabili sono invece sempre allocati nello heap i m k y 6 [0, 0, 0] h una persona int i = 6; int [ ] m = {0, 0, 0}; String k = "abcd"; persona y = new persona(); String h; h=k; crescita dello stack 59 Heap "abcd" – deallocati automaticamente solo quando non più riferiti da nessuna variabile Java 58 Java 60 La visione a "run-time" i m k y 6 Conseguenze allocazione stack vs. heap [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; • 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() { 1 d xxx Data d = new Data(1,1,1990); 1 return d; 1990 } public static void main(String args[]) { x xxx Data x = foo(); ... /*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!!!*/ crescita dello stack Java 61 Riassumendo: riferimenti e oggetti Java 62 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; } } • 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 63 Java 64 Chiamata di metodi -semantica- Invocazione dei metodi 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 • 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) 3. Esecuzione del corpo si tratta di puntatori agli oggetti, tranne che per il caso dei tipi primitivi Java 65 Passaggio parametri e reference: esempio 1 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); Date d1 = new Date(1,1,1990); d1.copiaIn(d2); Date.reinizializza (d1); AlQuando momento chiamata: la della funzione termina, d1 e' 31/12/1999 d1 xxx xxx quando copiaIn termina d2 è 3/2/1984 d 131 1 12 1990 1999 Java 66 Passaggio parametri e reference: esempio 2 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) { d Java d2 67 xxx xxx d1 1 1 1990 Java yyy 3 2 1984 68 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[] Dettagli sul linguaggio Java Java 69 Tipi array: dichiarazione e inizializzazione Java 70 Il caso degli array • Dichiarazione: • 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: int[] ai1, ai2; float[] af1; double ad[]; Persona[][] ap; – new <tipo> [<dimensione>] int[] i=new int[10], j={10,11,12}; float[][] f=new float[10][10]; Persona[] p=new Persona[30]; • Inizializzazione: int[] ai={1,2,3}; double[][] ad={{1.2, 2.5}, {1.0, 1.5}} Java • Se gli elementi non sono di un tipo primitivo, l’operatore “new” alloca solo lo spazio per i riferimenti 71 Java 72 Array di oggetti: Definizione A A Person[ ] Array di oggetti: Definizione person; Person[ ] person = new Person[20]; E’ E’definita definitasolo sololalavariabile variabile person. person.L’array L’arrayvero veroee proprio non esiste proprio non esiste person[0] = new Person( ); person B B Ora Oral’array l’arrayèèstato statocreato creatoma ma i idiversi diversioggetti oggettididitipo tipo Person non esistono ancora. Person non esistono ancora. person[0] = new Person( ); person 0 Java Person[ ] 1 2 3 4 73 16 17 18 19 Java 74 Array di oggetti vs. array di tipi base Array di oggetti: Definizione L’istruzione: person; person = new Person[20]; C C person; person = new Person[20]; person[0] = new Person( ); Un Unoggetto oggettodiditipo tipopersona personaèè stato statocreato creatoeeun unriferimento riferimento aatale taleoggetto oggettoèèstato stato inserito inseritoininposizione posizione0.0. 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 person L’istruzione: Person p[] = new Person[10]; 0 1 2 3 4 16 17 18 crea un oggetto di tipo array di Person e alloca spazio per 10 riferimenti a oggetti di tipo Person 19 f Non viene allocato spazio per gli oggetti veri e propri Person Java 75 0 1 Java 2 3 4 5 6 7 8 9 76 Loop generalizzato per collezioni Esempio per array • Se abbiamo una collezione C di elementi di tipo T, possiamo itrerare su di essi scrivendo for (T x: C) { 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; } esegui azioni su x } • anche gli array osno collezioni, quindi … Java 77 Accesso ad attributi e metodi locali Java 78 Visibilità dei nomi • 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; • Le variabili locali ad 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 void dipingi(String col) {colore=col;} ... } Java 79 Java 80 this per aggirare mascheramenti La pseudo-variabile this: esempio • 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”); • 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. • => a.marca diventa “Ford”, a.modello diventa “T4” Java 81 Java 82 Oggetti mutabili (richiamo...) this per restituire un reference • La pseudo-variabile this può essere utilizzata per restituire un riferimento all'oggetto corrente. • Lo stato degli oggetti mutabili può cambiare nel tempo, chiamando metodi opportuni • 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 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 83 Java 84 Oggetti immutabili La classe predefinita String • 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 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) • In generale, invece, gli oggetti sono mutabili: • (per avere stringhe mutabili si usa StringBuffer) int [ ] x = {0, 0, 0}; x[0] = 5; • 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 85 Java La classe String: esempio d’uso 86 Enumerazioni • 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! 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 istanzen non se ne possono costruire altre • • 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 – Non c’è bisogno di usare equal per confrontare I valori, basta == – 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;} }; – Costruttori invocati solo quando vengono costruite le costanti Java 87 Java 88 • 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 • 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(); } } 89 A partire dal peso di un corpo sulla terra, calcola e stampa il peso su tutti gli altri pianeti Java 90 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 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)); } – Attenzione: Il tipo del valore di restiuito 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: $ 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 • 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 91 Java 92 Overloading dei costruttori: esempio Overloading: un esempio class C { int f() {...} z C ref = new C(); int f(int x) {...} // corretto ref.f(); //distinguibile void f(int x) {...} // errato ref.f(5); //??? 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); } } C y 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 93 94 Ulteriore esempio Argomenti in numero variabile 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;} } • 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 Java 95 Java 96 Metodi e attributi di classe: vincoli Metodi e attributi di classe (o “statici”) • 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: • Un metodo static può accedere ai soli attributi e metodi static <nome classe>.<nome attributo> • Un metodo convenzionale può accedere liberamente a metodi e attributi static • 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 97 Java 98 Visione a run time 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); ...} } shape1 shape2 ... Shape.setScreen(new Screen()); // corretto Shape.show(); // errato, show è un metodo normale Shape s1=newShape(), s2=new Shape(); Screen s=new Screen(); s1.setScreen(s); // corretto, si possono chiamare // metodi static su oggetti shape3 // in questo punto s2.screen==s1.screen==s Java Allocato 99 Java 100 Tipi riferimento per i tipi primitivi Attributi costanti • Tipi primitivi comodi, ma a volte si preferirebbe usarli come riferimento, per omogeneità • Es. Java fornisce classi predefinite Integer, Character • È possibile definire attributi costanti tramite la notazione: final <definizione di attributo>=<valore> – 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) • 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 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 101 Java 102 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 Packages e Information Hiding – quindi si possono usare liberamente gli stessi nomi in packages diversi, senza generare confusione Java 103 Java 104 Information hiding in Java (1) 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 • – public • sono visibili a tutti con import • file deve avere stesso nome • al più una public class per ogni file – "friendly": 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 • Classi possono essere: • sono visibili solo all’interno dello stesso package/compilation unit • possono stare in file con altre classi 105 Java Information hiding (2) 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 106 107 • 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 108 Information hiding (3) Information hiding (4) • 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 • 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! 109 Java Il package java.lang 110 Ereditarietà • Il package java.lang contiene classi di uso molto frequente (String, Object, ecc.) • E’ possibile stabilire una relazione “sottoclasse_di (⊆) fra le classi di un programma Java – relaz. d’ordine parziale (riflessiva e transitiva) • Non è necessario importare le classi appartenenti al package java.lang prima di utilizzarle B public class D extends B {…} D • B classe base, o antenato, o padre, o sovraclasse, … • D classe derivata, o discendente, o figlio, o erede, o sottoclasse, … Java 111 Java 112 La relazione di ereditarietà Un semplice esempio • 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 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;} ... } 113 Java Overriding 114 Gerarchia a più livelli • 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: Figure perimeter area ClosedFigure public class AutomobileElettrica extends Automobile { ... public void accendi() {// OVERRIDING ….accensione auto elettrica e’ diversa da quella di auto a benzina… la reimplementiamo number_of_sides pixel width color scale rotate draw OpenFigure Ellipse Polygon Rectangle Java 115 Java 116 Esempio: Progetto tradizionale in C typedef struct …Figura; Figura Costruire software estendibile usando ereditarietà Cerchio Rettangolo Figura figure[100]; Triangolo figure[1] = “rettangolo”; figure[2] = “triangolo”; figure[3] = “cerchio”; 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” } } Java 117 Es. in versione OO Estendere progetto tradizionale: arriva il Trapezio Figura class Figura public void disegna(){…} typedef struct …Figura; Figura figure[100]; Cerchio Rettangolo Triangolo //si definisce classe (astratta) Figura, con metodo (astratto) disegna(), e sue eredi Rettangolo, Cerchio, Triangolo che implementano disegna() Figura figure = new Figura[100]; 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!!! class Cerchio public void disegna(){…} classRettangolo public void disegna(){…} 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(){…} Polimorfismo …disegnaTutto(figure); “Chiave” per costruire software estendile 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!!! Java Esempio: codice definito per sopraclasse funziona anche con sottoclasse Polimorfismo 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 • 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 .... Vediamo perché tutto funziona come ci aspettiamo…. Java 122 123 Java 124 Tipo apparente e tipo effettivo Esempio • 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 apparente e effettivo AutomobileElettrica • In Java, tipo effettivo può essere sottotipo del tipo apparente 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; Automobile myCar = new AutomobileElettrica(); il tipo statico di myCar è Automobile quello dinamico è AutomobileElettrica Java 125 Java 126 Uso delle classi dell’Esempio: assegnamento 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 pero’ garantisce che a run time non sorgono errori se invece si opera su un oggetto il cui tipo dinamico è un sottotipo del tipo statico Corretto: tipo (apparente) di mycar è sopratipo di tipo (apparente) di yourCar myCar 127 Ford false modello accesa Automobile myCar = new Automobile(“Ford”); AutomobileElettrica yourCar = new AutomobileElettrica(“El”); ... myCar = yourCar; yourCar Java xxx xxx El false Java true modello accesa 128 batterieCariche Uso delle classi dell’Esempio: assegnamento Uso delle classi dell’Esempio: assegnamento Corretto: tipo (apparente) di mycar è sopratipo del tipo effettivo 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 Automobile myCar = new AutomobileElettrica(); 129 Uso delle classi dell’Esempio: assegnamento Java 130 Uso delle classi dell’Esempio: chiamata di un metodo Scorretto: tipo (apparente) di yourCar non è sopratipo del tipo del costruttore 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 AutomobileElettrica yourCar = new Automobile (); Java 131 Java 132 Esempio 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. 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 133 Java Dispatching (binding dinamico) Implementazione dispatching • Esempio: static void partenza (Automobile p) { //accende p e la fa partire p.accendi(); ... 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 puntatore al codice a1 a2 accendi – varie tecniche implementative: per es. ogni oggetto può contenere un puntatore al codice dei propri metodi Java 134 puntatore al codice 135 Java 136 Overloading e overriding Regola per chiamata metodi • Binding dinamico non va confuso con overloading class Punto2D{ public float distanza(Punto2D p){…} } class Punto3D extends Punto2D { public float distanza(Punto 3D 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 137 • 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 P del parametro attuale p e al tipo statico X di x. • 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 138 Esempio class Punto2D{public float distanza(Punto 2D){…}…} class Punto3D extends Punto2D {public float distanza(Punto 3D){…}} 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)); Classi e tipi metodo di Punto2D Punto2D Punto2D Punto2D Punto2D Punto3D Java 139 Java 140 Possibili errori di tipo in un linguaggio 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 • Assegnamenti tra oggetti di tipo incompatibile • Chiamate di metodi incompatibili (numero e tipo dei parametri) Java 141 Java Ancora sui tipi in Java 142 Gerarchia di tipi • 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 • Una classe definisce un tipo • Una sottoclasse (transitivamente) definisce un sottotipo • Un oggetto del sottotipo è sostituibile a un oggetto del tipo • Si distingue tra – tipo statico • il tipo dichiarato – tipo dinamico (o attuale) Java garantisce che ciò non comprometta la type safety (vedremo come) • il tipo dell'oggetto attualmente assegnato Java 143 Java 144 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 Dettagli su ereditarietà in Java Java 145 Accesso ai membri private 146 Overriding e la pseudo variabile super • Le sottoclassi non possono accedere agli attributi (e metodi) private delle sopraclassi! • è sbagliato scrivere: • 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>) public void accendi() { if(batterieCariche) accesa=true; ...;} – Esempio: public class AutomobileElettrica extends Automobile { ... public void accendi() {// OVERRIDING if(batterieCariche) super.accendi(); else System.out.println("Batterie scariche");} } perché accesa è private nella sovraclasse! Java Java 147 Java 148 Accesso ed ereditarietà 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: • 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 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!) – 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 – In AutomobileElettrica: 149 public AutomobileElettrica(String modello) { super(modello); //qui inizializza modello e accesa batterieCariche=false; } Java La classe Object 150 Equals • 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(); • 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 151 Java 152 toString clone() public String toString ( ) • Restituisce una rappresentazione testuale dell’oggetto • Implementazione fornita da Object: • Il metodo clone restituisce una copia dell'oggetto, nello stesso stato – una stringa che mostra il nome del tipo seguito da una rappresentazione testuale del valore dell’oggetto – 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) – 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 153 Java 154 Classi e metodi astratti Classi e information hiding Attributi e metodi di una classe • se non dico nulla sono visibili all'interno del package: chiamato informalmente “friendly” • se no, possono essere: – public • visibili a tutti – protected • visibili all'interno del package • visibili alle sottoclassi anche se esterne al package • 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 • ...ci ritorneremo! – private • visibili solo all’interno della stessa classe Java 155 Java 156 Classi e metodi final Classi e metodi astratti: esempio • Se vogliamo impedire che sia possibile creare sottoclassi di una certa classe la definiremo final abstract class Shape { static Screen screen; Shape(Screen s) {screen=s;} abstract void show(); } class Circle extends Shape { void show() { ... } } ... Shape s=new Shape(); // errato Circle c=new Circle(); // corretto Java • 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 } 157 I limiti dell’ereditarietà semplice Java 158 La soluzione di Java • L’ereditarietà semplice non permette la descrizione di numerose situazioni reali • Esempio: • Distingue tra una gerarchia di ereditarietà (semplice) ed una gerarchia di implementazione (multipla) … • … introducendo il costrutto interface – Supponiamo di avere una classe Giocattolo ed una classe Automobile. In assenza di ereditarietà multipla non posso definire la classe AutomobileGiocattolo Java 159 Java 160 Interfacce Attributi costanti • Una interfaccia è come una classe che può avere solo attributi costanti e i cui metodi sono tutti pubblici ed astratti • Sintassi: • Implicitamente, gli attributi dichiarati in una interfaccia sono – visibili alla classe che la implementa – immodificabili (static final) interface <nome> { <lista di definizione di attributi costanti e metodi privi di corpo> } interface scalable { int SMALL=0,MEDIUM=1,BIG=2; //static e final void setScale(int SIZE); } Java 161 Java 162 Interfacce ed ereditarietà La gerarchia di implementazione • Una interfaccia può ereditare da una o più interfacce • Sintassi: interface <nome> extends <nome1>,..,<nomen> { ... } • 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 163 Java 164 Promozioni Conversioni automatiche di tipo Java 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 165 Java Casting in generale Conversioni forzate: casting • È possibile forzare esplicitamente la conversione da un tipo riferimento T ad un sottotipo T1 purché: •È possibile forzare una conversione di tipo attraverso l’operatore di casting: – Il tipo dinamico dell’espressione che convertiamo sia un sottotipo di T1 (<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 166 • 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) char char, int char, int, long char, int, long, float 167 Java 168 instanceof Esempio: instanceof e ridefinizione di equals • 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 169 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 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 170 • 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 • ArrayList sono contenitori “estendibili” e “accorciabili” dinamicamente • NB ArrayList sono implementati come array di puntatori => metodo add allunga di 1 la (parte utilizzata del) l’array – 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) – 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, Bob team.remove(0); // rimuove Mary, ora ci sono Sue e Bob ArrayList <Person> team = new ArrayList<Person>(); – crea ArrayList privo di elementi e anche di “posizioni” per memorizzarli – 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 • 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 ma solo per sostituire un oggetto già presente in quella posizione 171 Java 172 • ArrayList sono contenitori => si possono scandire gli elementi usando ciclo for-each for(Person p: team){ //fa qualcosa con la persona p } 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 è equivalente al tradizionale for ArrayList<Integer> intList = newArrayList<Integer>(); intList.add(7); for(int i=0; i<team.size(); i++){ Person p = team.get(i); //fa qualcosa con la persona p } – 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 173 Vector (Java<1.5) 174 I/O formattato • Coi Vector (nelle versioni precedenti di Java) si possono memorizzare solo oggetti Object, quindi in aggiunta occorre anche un casting prima di effettura un “unwrap” Vector myVector = new Vector (); myVector.add(new Integer(5)); int n = ((Integer)myVector.get(0)).intValue(); Java Java • 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); */ ... 175 Java 176