I nomi in Java F. Bombi 18 novembre 2003 18 novembre 2003
Le variabili Una variabile è una posizione in memoria alla quale è associato un determinato tipo che può essere o un tipo primitivo o un riferimento Una variabile ha sempre un valore compatibile con il suo tipo Il valore di una variabile può essere modificato da un’assegnazione oppure da un operatore di incremento (++) o decremento (--) 18 novembre 2003
I 7 tipi di variabili Variabile di classe: un campo di una classe con l’attributo static Variabile di esemplare: un campo di una classe senza l’attributo static Componenti di un array: sono variabili senza nome che vengono create quando si crea un array destinate a contenere i singoli elementi dell’array Parametro di un metodo: nome di una variabile che sarà inizializzata con il valore dell’argomento passato al metodo Parametro di un costruttore: come per un metodo Parametro di un gestore di eccezione Variabile locale: variabile dichiarata all’interno di un blocco 18 novembre 2003
Variabile di classe class A { static int vs; public A () {vs++;} … } A uno = new A(); A due = new A(); System.out.println(A.vs); Una variabile di classe viene creata al momento in cui la classe viene caricata nella memoria della macchina virtuale e in assenza di indicazione contraria viene inizializzata a 0, false o null Di una variabile di classe esiste un solo esemplare condiviso da tutti gli oggetti della classe L’uso di variabili di classe è limitato a casi molto specifici ad esempio per costanti simboliche condivise oppure per contare quanti esemplari di una classe sono stati creati o per segnalare ad altri esemplari di una classe che si è invocato un metodo 18 novembre 2003
Variabili di esemplare Le variabili di esemplare (instance variable) rappresentano il caso più comune di campo di una classe Un nuova variabile di esemplare viene creata ogni volta che si crea un esemplare della classe che la contiene, in assenza di indicazione contraria, le variabili di esemplare sono sempre inizializzate a 0, false o null Un variabile di esemplare cessa di esistere quando l’oggetto che la contiene non è più indirizzato da un riferimento (lo spazio di memoria è a questo punto soggetto a garbage collection) 18 novembre 2003
Componenti di un array class Coppia { int a; int b;} … Coppia[] v = new Coppia[4]; v[0].a = 25; // null pointer class Coppia { int a; int b;} … Coppia[] v = new Coppia[4]; for(int i=0;i<4;i++) v[i] = new Coppia(); v[0].a = 25; // OK Le componenti di un array sono variabile senza nome che vengono create al momento della creazione dell’array. Sono inizializzati a 0, false o null Fare attenzione che se si crea un array di oggetti la creazione dell’array crea solo i riferimenti agli oggetti (inizializzati a null) e non gli oggetti che dovranno essere esplicitamente creati uno per uno 18 novembre 2003
I parametri parametro class A { void m(int i) { int locale; locale = i*i; } Un parametro attribuisce un nome all’interno di un metodo all’argomento con il quale il metodo viene invocato Un parametro, analogamente ad una variabile locale, viene creato al momento dell’invocazione del metodo e cessa di esistere quando si conclude il blocco che costituisce il corpo del metodo Un parametro viene inizializzato con il valore dell’argomento usato al momento della chiamata del metodo Java di conseguenza utilizza sempre e solo il passaggio dei parametri per valore … A x = new A(); int k = 25; x.m(k); x.m(3); argomento i = k = 25 18 novembre 2003
Il passaggio dei parametri per valore è unidirezionale static void scambia (int x, int y) { int tmp = x; x = y; y = tmp; } // Il passaggio dei parametri in Java public class Param { public static void main (String[] arg) { int i = 1; int j = 2; System.out.println("Prima: i= " + i + " j= " + j); scambia(i, j); System.out.println("Dopo1: i= " + i + " j= " + j); Integer ii = new Integer(i); Integer jj = new Integer(j); scambia(ii, jj); System.out.println("Dopo2: i= " + ii + " j= " + jj); MioInt iii = new MioInt(); iii.valore = i; MioInt jjj = new MioInt(); jjj.valore = j; scambia(iii, jjj); System.out.println("Dopo3: i= " + iii + " j= " + jjj); } static void scambia (MioInt x, MioInt y) { int tmp = x.valore; x.valore = y.valore; y.valore = tmp; } class MioInt { int valore; public String toString() { return Integer.toString(valore); } static void scambia (Integer x, Integer y) { Integer tmp = x; x = y; y = tmp; } Prima: i= 1 j= 2 Dopo1: i= 1 j= 2 Dopo2: i= 1 j= 2 Dopo3: i= 2 j= 1 18 novembre 2003
1 i ? x tmp inizializza 1 2 ? 1 2 j ? y 1 2 al ritorno del metodo int i = 1; int j = 2; 1 i ? x tmp inizializza 1 2 ? 1 2 j ? y 1 2 scambia(i, j); static void scambia (int x, int y) { int tmp = x; x = y; y = tmp; } al ritorno del metodo scambia le variabili i e j sono immutate 18 novembre 2003
x y tmp inizializza ii jj 1 2 al ritorno del metodo Integer ii = new Integer(1); Integer jj = new Integer(2); x y tmp inizializza ii jj 1 2 static void scambia (Integer x, Integer y) { Integer tmp = x; x = y; y = tmp; } scambia(ii,jj); al ritorno del metodo scambia i riferimenti ii e jj sono immutati 18 novembre 2003
iii e jjj non sono cambiati sono però stati scambiati i campi MioInt iii = new MioInt(); iii.valore = 1; MioInt jjj = new MioInt(); jjj.valore = 2; x y tmp Valore ? iii inizializza 1 2 1 Valore ? jjj 2 1 static void scambia (MioInt x, MioInt y) { int tmp = x.valore; x.valore = y.valore; y.valore = tmp; } scambia(iii, jjj); al ritorno del metodo scambia i riferimenti iii e jjj non sono cambiati sono però stati scambiati i campi 18 novembre 2003
Come ritornare un valore Il meccanismo del passaggio dei parametri ad un metodo essendo per valore è unidirezionale, il parametro viene inizializzato con l’argomento al momento della chiamata, se il parametro viene modificato l’argomento rimane immutato Un metodo può restituire una sola variabile con la clausola return, il tipo di valore restituito deve essere indicato nella dichiarazione del metodo 18 novembre 2003
Uso di variabili globali All’interno di una classe due metodi possono scambiarsi dati utilizzando un campo della classe come variabile condivisa o globale Questa forma di comunicazione non può essere usata fra metodi di classi diverse in quanto gli esemplari di classi diverse non accedono agli stessi campi Ricordarsi infine che un metodo quando è invocato conosce i campi dell’esemplare della classe individuata dal riferimento usato per invocare il metodo detto talvolta parametro implicito Il valore del parametro implicito è accessibile con la clausola this 18 novembre 2003
all’attivazione o chiamata del metodo argomento actual parameter parametro reale parametro formal parameter parametro formale all’attivazione o chiamata del metodo il valore dell’argomento viene utilizzato per inizializzare il corrispondente parametro return valore; Alla terminazione del metodo il controllo viene passato al punto di chiamata. I valore dei parametri vengono persi, in quanto si tratta di variabili locali allocate sul run-time stack (ma non vengono persi eventuali effetti collaterali). Il metodo può restituire un singolo valore 18 novembre 2003
Pacchetti (package) Una applicazione Java si compone di uno o più pacchetti Un pacchetto è un insieme di file (unità compilabili), un pacchetto può essere organizzato gerarchicamente in sottopacchetti In assenza di indicazioni contrarie i file contenuti nel directory di lavoro costituiscono un pacchetto senza nome Tutti gli esempi visti a lezione sono realizzati come pacchetti senza nome Abbiamo visto in molti esempi come importare un componente di un pacchetto di libreria o l’intero pacchetto 18 novembre 2003
I nomi in Java Tutte le entità usate in un programma Java sono individuate da nomi, dette identificatori, composti da stringhe di caratteri alfanumerici con il primo carattere alfabetico Ogni nome deve essere dichiarato prima di essere usato, la dichiarazione specifica l’entità cui il nome si riferisce e chi può accedere al nome qualificandolo Ogni nome semplice è riconosciuto in una porzione del programma detta il suo scope Il significato di un nome è stabilito dal contesto nel quale viene usato 18 novembre 2003
Le entità individuate da un nome possono essere public class Coppia implements Comparable { public Comparable chiave; … } public interface Stack { void push (Object x); … } Le entità individuate da un nome possono essere Una classe Un’interfaccia Un membro di una classe (campo o metodo) Un parametro di un metodo, di un costruttore o di un gestore di eccezione Una variabile locale Un nome semplice è costituito da un solo identificatore Un nome qualificato è costituito da più identificatori separati da un • (punto) public class Coppia implements Comparable { public Comparable chiave; … public String toString () { return chiave.toString() + ":" + attributo.toString(); } } public class Coppia implements Comparable { … public Coppia (Comparable c, Object a) { chiave = c; attributo = a; } public int compareTo (Object x) { return chiave.compareTo(((Coppia)x).chiave); } } public static void main (String[] arg) throws IOException { BufferedReader in = new BufferedReader(new FileReader(arg[0])); int n = 0; int somma = 0; … while ((str = in.readLine()) != null) { token = new StringTokenizer(str, ":"); int matricola = Integer.parseInt(token.nextToken()); String nome = token.nextToken(); dati[n++] = new Studente(nome, matricola); } 18 novembre 2003
Lo scope int[] r = c.contiene(); for (int i = 0; i < r.length; i++) System.out.print(r[i] + ", "); System.out.println(); c.togli(1); r = c.contiene(); for (int i = 0; i < 32; i++) if (c.appartiene(i)) System.out.print("1"); else System.out.print("0"); public class Coppia implements Comparable { public Object attributo; public Coppia (Comparable c, Object a) { chiave = c; attributo = a; } public int compareTo (Object x) { return chiave.compareTo(((Coppia)x).chiave); } public String toString () { return chiave.toString() + ":" + attributo.toString(); } public Comparable chiave; } Lo scope di una dichiarazione è la regione di un programma nell’ambito della quale ci si può riferire ad una entità con il nome semplice Lo scope del nome di una classe è l’intero pacchetto nel quale la classe compare Lo scope di un membro di una classe è l’intera classe nella quale è dichiarato Lo scope del nome di un parametro di un metodo o di un costruttore è l’intero corpo del metodo o del costruttore Lo scope di una variabile locale dichiarata in un blocco si estende fino alla fine del blocco Lo scope di una variabile locale dichiarata nell’inizializzazione di un ciclo for si estende al solo corpo del ciclo 18 novembre 2003
Il controllo dell’accesso In genere i campi di una classe sono private (o protected) in quanto si vuole che solo i metodi della classe possano modificare il valore dei campi mentre i metodi sono public perché devono poter essere utilizzati dall’esterno della classe L’uso di nomi qualificati consente di controllare l’accesso ai membri di una classe indipendentemente dallo scope, si danno 4 casi: default: accesso a livello di package public: accesso da qualsiasi punto private: accesso possibile solo dall’interno della classe nella quale il nome è dichiarato protected: accesso a livello di package e nelle sottoclassi che estendono la classe 18 novembre 2003
Oscurare una dichiarazione La dichiarazione di un nome all’interno di un blocco contenuto in un altro blocco può oscurare la dichiarazione più esterna In genere è bene evitare di dichiarare la stessa variabile in blocchi diversi con significati diversi per evitare confusione (il compilatore non si confonde! ma il lettore può rimanere confuso) Vediamo ora un esempio di una dichiarazione che ne oscura un’altra in una situazione particolarmente insidiosa 18 novembre 2003
public class StackAr implements Stack { private Object[] v; private int sp; private static final int MAX = 10; public StackAr () { sp = 0; Object[] v = new Object[MAX]; } public StackAr (int max) v = new Object[max]; public void push (Object x) { v[sp++] = x; } public Object pop () throws Underflow { if (sp == 0) throw new Underflow("Pop di stack vuoto"); else return v[--sp]; public Object testa () throws Underflow throw new Underflow("Testa di stack vuoto"); return v[sp-1]; public boolean vuoto () { return sp == 0; } 18 novembre 2003
I paradigmi di programmazione Un programma deve essere comprensibile sia al compilatore (e alla macchina) sia all’uomo È importante utilizzare paradigmi (modelli) di programmazione che aiutino a scrivere programmi corretti e facili da mantenere e modificare Nel tempo si sono sviluppati molti paradigmi diversi nel tentativo di rendere più facile la produzione di programmi corretti e mantenibili 18 novembre 2003
La decomposizione funzionale Per decomposizione funzionale si intende la tecnica con la quale si risolve un problema attraverso la composizione di sottoprogrammi o funzioni Ad esempio dovendo leggere ed elaborare dei dati si scrive un sottoprogramma che legge i dati, uno che gli elabora ed un terzo che stampa i risultati Il primo linguaggio che ha messo a disposizione strumenti per facilitare la decomposizione funzionale è stato il Fortran IV (fine anni ’50) 18 novembre 2003
La programmazione strutturata Un programma si dice strutturato se è realizzato dalla composizione delle due sole strutture (o loro derivazione) if-then-else e while-do I linguaggi di programmazione moderni (sviluppati dopo l’invenzione del Pascal, inizio anni ‘70) sono per loro natura strutturati e quindi obbligano ad utilizzare naturalmente la programmazione strutturata L’idea è così connaturata con i linguaggi moderni quali il C/C++ o Java che non è quasi più il casi di parlare di programmazione strutturata 18 novembre 2003
La modularizzazione Per affrontare la costruzione di un grande progetto software è necessario disporre di strumenti che consentano di costruire il software sotto forma di componenti indipendenti detti talvolta moduli Pascal (in origine) non consentiva nessuna forma di modularizzazione, un programma doveva essere sempre pensato come monolitico in quanto doveva contenere al suo interno tutte le procedure e funzioni necessarie Il linguaggio C non dispone intrinsecamente di strumenti per la modularizzazione ma non la impedisce, è quindi possibile realizzare in C software modulare utilizzando le funzioni di macro elaborazione fornite dal linguaggio e strumenti esterni quali make per automatizzare le operazioni di espansione delle macro, compilazione e collegamento 18 novembre 2003
Modularizzazione (segue) I linguaggi Ada e Modula 2 sono stati progettati in modo da facilitare e rendere controllabile la modularizzazione. Hanno però avuto uno sviluppo limitato, il primo solo nell’ambiente delle commesse militari e spaziali, il secondo solo in un limitato ambiente accademico Alcune estensione del linguaggio C quali C++ e in particolar modo il linguaggio orientato agli oggetti Eifell sono stati pensati in modo da facilitare la modularizzazione 18 novembre 2003
La programmazione orientata agli oggetti I linguaggio orientati agli oggetti mettono a disposizione una ricco repertorio di strumenti per la realizzazione di software in forma modulare Package Incapsulamento (o information hiding) Polimorfismo Ereditarietà 18 novembre 2003
Packages -> Pacchetti Un programma in Java è organizzato come un insieme di pacchetti Ogni pacchetto ha un suo insieme di nomi per i tipi (classi e interfacce) Un tipo dichiarato in un pacchetto è accessibile al di fuori del pacchetto in cui è stato dichiarato solo se ha l’attributo public I pacchetti sono organizzati in forma gerarchica come pacchetti e sottopacchetti I pacchetti possono essere memorizzati come file o in un database L’organizzazione in pacchetti facilita la modularizzazione isolando le scelte dei nomi di un pacchetto da quelle di ogni altro pacchetto 18 novembre 2003
Incapsulamento Per incapsulamento o information hiding si intende la caratteristica di un linguaggio che consente di nascondere all’utente di un pacchetto (o anche in particolare di una sola classe) i dettagli con cui le funzionalità sono realizzate Java consente di progettare pacchetti e classi in modo da nascondere in modo completo i dettagli realizzativi all’utente 18 novembre 2003
Polimorfismo Il polimorfismo è la proprietà di un linguaggio orientato ad oggetti per cui la decisione di quale metodo viene invocato tramite un riferimento viene stabilito al momento dell’esecuzione del programma in funzione del valore effettivamente assegnato al riferimento (in sintesi si parla di late binding) Java è intrinsecamente polimorfo Una forma elementare di polimorfismo è anche offerta dal sovraccarico (overloading) del nome di un metodo. Notare che il sovraccarico non richiede il late binding in quanto il compilatore può decidere quale metodo invocare dal confronto della forma della chiamata con la firma del metodo (nome e elenco del tipo degli argomenti) 18 novembre 2003
Ereditarietà L’ereditarietà consente di costruire una nuova classe che estende le funzionalità di un’altra classe senza avere accesso al codice sorgente della classe che si vuole estendere Ereditarietà e polimorfismo sono funzionalità da utilizzare in modo coordinato La programmazione orientata ad oggetti si caratterizza dalla possibilità di costruire l’estensione di una classe senza disporre del codice sorgente della classe da estendere combinata con la realizzazione del polimorfismo mediante late binding 18 novembre 2003