320 likes | 501 Views
I nomi in Java. F. Bombi 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
E N D
I nomi in Java F. Bombi 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 (--)
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
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
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)
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
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
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
inizializza i j y x ? 1 2 ? int i = 1; int j = 2; tmp 1 2 ? 1 2 1 static void scambia (int x, int y) { int tmp = x; x = y; y = tmp; } scambia(i, j); al ritorno del metodo scambia le variabili i e j sono immutate
x tmp inizializza ii jj y 1 2 Integer ii = new Integer(1); Integer jj = new Integer(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
x tmp inizializza iii Valore ? y jjj Valore ? static void scambia (MioInt x, MioInt y) { int tmp = x.valore; x.valore = y.valore; y.valore = tmp; } MioInt iii = new MioInt(); iii.valore = 1; MioInt jjj = new MioInt(); jjj.valore = 2; 1 1 2 1 2 scambia(iii, jjj); al ritorno del metodo scambia i riferimenti iii e jjj non sono cambiati sono però stati scambiati i campi
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
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
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 argomento actual parameter parametro reale parametro formal parameter parametro formale
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
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
public class Coppia implements Comparable { public Comparable chiave; … } public interface Stack { void push (Object x); … } 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); } • 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)
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 < r.length; i++) System.out.print(r[i] + ", "); System.out.println(); 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
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
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
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) { sp = 0; 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 { if (sp == 0) throw new Underflow("Testa di stack vuoto"); else return v[sp-1]; } public boolean vuoto () { return sp == 0; } }
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
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)
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
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
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
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à
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
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
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)
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