Programmazione ad oggetti - Dipartimento di Ingegneria dell
Transcript
Programmazione ad oggetti - Dipartimento di Ingegneria dell
Programmazione ad oggetti Rosario Pugliese [email protected] Università di Firenze Programmazione ad oggetti – p.1/82 Programmazione orientata agli oggetti Programmazione procedurale: programma organizzato intorno alle procedure che manipolano i dati. Programmazione orientata agli oggetti: programma organizzato intorno ai dati da manipolare. Programma OO: insieme di classi (tipi di dato) che caratterizzano il comportamento di tutti i dati. Gli oggetti e le variabili di una classe corrispondono alle entità fisiche e logiche presenti nel dominio relativo al particolare problema considerato. Gli oggetti di una classe vengono manipolati eseguendo i metodi della classe stessa, cioè inviando messaggi agli oggetti. I messaggi rappresentano le azioni esercitate sugli oggetti. Programmazione ad oggetti – p.2/82 Cos’è una classe Una classe è un “prototipo” che definisce i campi ed i metodi comuni a tutti gli oggetti di un certo tipo. La classe dei numeri razionali dichiara due variabili, num e den, ed i metodi div, mol, ... che permettono ad un altro oggetto di dividere, moltiplicare, ... un numero razionale con un altro. Programmazione ad oggetti – p.3/82 Cos’è un oggetto Una ‘scatola’ software contenente membri, ovvero variabili (anche dette campi o attributi) e metodi. Un oggetto è una istanza della classe a cui appartiene. Programmazione ad oggetti – p.4/82 Un oggetto di tipo razionale Lo stato dell’oggetto è formato dai campi num e den che rappresentano numeratore e denominatore. I metodi di istanza (div, mol, . . . ) cambiano lo stato eseguendo operazioni tra l’oggetto ed un altro simile. Programmazione ad oggetti – p.5/82 Istanze di una classe Quando si crea un oggetto, il sistema alloca sufficente memoria per l’oggetto e le sue (copie delle) variabili di istanza. Le classi sono entità statiche (definite prima dell’esecuzione), gli oggetti sono entità dinamiche (che esistono solo durante l’esecuzione). Programmazione ad oggetti – p.6/82 Cos’è un messaggio Quando un oggetto A vuole che un oggetto B esegua uno dei metodi di B , A invia un messaggio a B contenente il nome del metodo da eseguire e gli eventuali parametri. Programmazione ad oggetti – p.7/82 Definizione di una classe Dichiarazione della classe e corpo della classe. class nome Classe blocco La dichiarazione contiene perlomeno il nome della classe. Il corpo contiene il codice che regola il ciclo di vita degli oggetti istanziati dalla classe, tra cui dichiarazioni di variabili di istanza, definizione dei metodi che implementano il comportamento degli oggetti della classe. Programmazione ad oggetti – p.8/82 Dichiarazione di variabili di tipo classe La sintassi è simile a quella usata per dichiarare variabili di un tipo di dato primitivo. Es. Dichiarazione di un oggetto di tipo Razionale Razionale numeroRazionale; Il significato è simile alla dichiarazione di una variabile di tipo array. Una variabile di tipo classe non contiene un oggetto di quella classe ma l’indirizzo (riferimento) dell’area di memoria dove l’oggetto è memorizzato. La dichiarazione di una variabile di tipo classe non crea alcun oggetto: il riferimento corrispondente sarà null fino a quando l’oggetto non sarà creato. Programmazione ad oggetti – p.9/82 Creazione di oggetti Una istanza di una classe si crea mediante l’operatore new (come nel caso degli array) che prende come argomento l’invocazione di un metodo (detto costruttore) con lo stesso nome della classe. new nome Classe ( lista Parametri ) L’operatore new alloca la memoria per le variabili di istanza e restituisce un riferimento all’oggetto creato (che viene assegnato ad una variabile di tipo appropriato). Es. Creazione di un oggetto di tipo Razionale con numeratore pari a 3 e denominatore pari a 4. Razionale numeroRazionale; numeroRazionale = new Razionale( 3,4 ); Razionale numeroRazionale = new Razionale( 3,4 ); Programmazione ad oggetti – p.10/82 Distruzione di oggetti Non esistono operatori ad-hoc. Un oggetto privo di riferimenti non è più accessibile e diviene memoria da riciclare (compito del garbage collector). Razionale R = new Razionale( 10,15 ); R = new Razionale( 15,10); Programmazione ad oggetti – p.11/82 Usare gli oggetti Nome qualificato di un campo/metodo: nome dell’oggetto, seguito da un punto e dal nome del campo/metodo. Per accedere alle variabili ed ai metodi di un oggetto dall’eseterno della definizione della sua classe. nome Oggetto.nome Variabile nome Oggetto.nome Metodo ( lista Par ) Esempio numeroRazionale.num numeroRazionale.maggiore ( altroNumRaz ) Passaggio per riferimento: come per gli array, un oggetto passato come argomento ad un metodo può essere modificato dalle istruzioni contenute all’interno del metodo. Così come i parametri, anche i valori di ritorno di tipo classe sono riferimenti ad oggetti. Programmazione ad oggetti – p.12/82 Oggetti come parametri formali Consideriamo il seguente frammento di codice class Razionale { int num; int den; Razionale( int n, int d ) { num = n; den = d; } void setNum( int n ) { num = n; } int getNum( ) { return num } } void cambiaNumeratore( Razionale r,int n ) { r.setNum( n ); } Programmazione ad oggetti – p.13/82 Oggetti come parametri formali Cosa succede dopo le seguenti istruzioni Razionale numeroRazionale = new Razionale( 3,4 ); cambiaNumeratore( numeroRazionale,5 ); Al termine dell’invocazione del metodo cambiaNumeratore, il numeratore del numero razionale, ovvero la variabile di istanza num dell’oggetto numeroRazionale, risulterà essere modificato: il suo valore non sarà più 3 (come era prima dell’invocazione), ma 5. Programmazione ad oggetti – p.14/82 Oggetti come parametri formali Creando un nuovo oggetto per poi assegnare al parametro formale il riferimento al nuovo oggetto, non si modifica l’oggetto originale. void cambiaRazionale( Razionale r,int n,int d ) { Razionale h = new Razionale( n,d ); r = h; } Programmazione ad oggetti – p.15/82 Uguaglianza di oggetti Poiché le variabili di oggetto sono riferimenti, un test di uguaglianza del tipo p1==p2 su due variabili di oggetto p1 e p2 verifica solo se due riferimenti puntano allo stesso oggetto, non se due oggetti hanno lo stesso valore! Per controllare se due oggetti hanno lo stesso valore si usa il metodo equals(), predefinito per tutte le classi: p1.equals(p2). Programmazione ad oggetti – p.16/82 Costruttori Sono metodi che hanno lo stesso nome della classe. Creano un oggetto e ne inizializzano i campi. Sintassi nome Classe ( lista Parametri ) Non hanno tipo di ritorno. Esempio Razionale( int n,int d ) { num = n; den = d; } Programmazione ad oggetti – p.17/82 Costruttori Possono esserci più costruttori per una stessa classe, purché si differenzino per numero e/o tipo dei parametri (come vedremo, devono avere firma diversa). Se nessun costruttore viene esplicitamente definito dal programmatore, Java ne crea automaticamente uno senza parametri (detto di default). Tuttavia, se il programmatore definisce almeno un costruttore, Java lascia al programmatore il compito di definire il costruttore di default (ovvero, quello senza parametri). Programmazione ad oggetti – p.18/82 Metodo main Metodo da cui la classe inizia automaticamente l’esecuzione. Deve rispettare la seguente sintassi (altrimenti, viene segnalato un errore) public static void main( String[] args ) blocco Si noti che il metodo ha per argomento un riferimento ad un oggetto che è un vettore di stringhe. Programmazione ad oggetti – p.19/82 Esempio: main con argomenti public class Argomenti { public static void main( String args[] ) { for (int i=0; i<args.length; i++) System.out.println(args[i]); } } La classe precedente costituisce un semplice programma Java che, quando invocato con un numero qualsiasi di argomenti, termina mostrandoli sullo schermo su righe successive. Programmazione ad oggetti – p.20/82 Metodo main Un programma Java è una classe che contiene il metodo main. Quando si invoca l’interprete Java (ovvero il comando java) specificando il nome di una classe viene eseguito il metodo main della classe (se non esiste, viene segnalato un errore). La parte di definizione della classe che non è espressamente usata nel metodo main verrà ignorata. Questo tipo di utilizzo è utile per ottenere un programma che verifichi il comportamento di quella classe. Però è meglio non includere il metodo main nella definizione di una classe che abbia il solo scopo di definire un tipo di dato. Programmazione ad oggetti – p.21/82 Accessori e mutatori Metodi che consentono ad oggetti esterni alla classe la lettura e la modifica di campi (in modo sicuro). Sintassi tipo Variabile getnome Var ( ) blocco void setnome Var ( lista Parametri ) blocco Esempio int getNum( ) { return num; } void setNum( int n ) { num = n; } // accessore // mutatore Programmazione ad oggetti – p.22/82 La classe String Abbiamo già usato letterali di questo tipo di dato: "Buongiorno", "Benvenuti nel nostro programma!". Una variabile messaggio di tipo String si dichiara così: String messaggio e si inizializza così: messaggio = "Buongiorno"; oppure, direttamente, così: String messaggio = "Buongiorno"; Poiché una variabile di tipo String non è una variabile di tipo primitivo può essere inizializzata usando dei costruttori. String messaggio = new String( "Buongiorno" ); Gli oggetti di classe String sono immutabili. Programmazione ad oggetti – p.23/82 Creazione di stringhe Una volta creati, gli oggetti String non subiscono mai variazioni. Possono essere concatenati ad oggetti della stessa classe, oppure possono essere usati per selezionare una sottostringa, ecc., ma in questi casi vengono sempre creati nuovi oggetti String. Esempio: s = nome.concat(secnome).concat(cognome) Il messaggio concat(secnome) inviato all’oggetto nome restituisce un riferimento ad un nuovo oggetto String (es. Carlo Azelio). Il messaggio concat(cognome) inviato al secondo oggetto crea ancora un nuovo oggetto String (es. Carlo Azelio Ciampi). Programmazione ad oggetti – p.24/82 Creazione di stringhe Se si assegna lo stesso letterale "Salve!" a due variabili s e t di tipo String, allora le due variabili faranno riferimento alla stessa area di memoria. String s = "Salve!"; String t = "Salve!"; Ogni invocazione del costruttore String, invece, crea un nuovo oggetto. String u = new String( "Salve!" ); String v = new String( "Salve!" ); Come conseguenza dell’esecuzione delle due istruzioni precedenti, vengono assegnati due indirizzi di memoria ad u e v che riferiscono due aree di memoria distinte che ospitano entrambe il valore "Salve!". Programmazione ad oggetti – p.25/82 Creazione di stringhe La verifica della diversità delle due situazioni si può fare controllando l’uguaglianza delle variabili nei due casi e stampando poi sullo schermo il risultato ottenuto: s e t hanno lo stesso valore, mentre u e v no. Lo stesso risultato si può ottenere con le istruzioni System.out.println( v.equals( u ) ); System.out.println( t.equals( s ) ); Programmazione ad oggetti – p.26/82 Metodi della classe String Alcuni dei metodi della classe String length( ): restituisce la lunghezza della stringa contando i caratteri in essa inclusi, compresi spazi e caratteri speciali; toLowerCase( ) (toUpperCase( )): restituisce la stringa in caratteri minuscoli (maiuscoli); equalsIgnoreCase( String altraStringa ): restituisce true se la stringa di cui viene invocato il metodo e la stringa in input sono uguali (ignorando la differenza tra maiuscole e minuscole); compareTo( String stringa ): confronta l’ordine lessicografico tra la stringa di cui viene invocato il metodo e la stringa definita dal parametro stringa e restituisce: -1 se il parametro precede la stringa, 0 se sono uguali e 1 altrimenti; Programmazione ad oggetti – p.27/82 Metodi della classe String trim( ): restituisce una stringa uguale a quella di cui viene invocato il metodo ma ripulita da spazi bianchi esterni alla stringa stessa; charAt( int indice ): restituisce il carattere della stringa nella posizione specificata dal parametro indice; substring( int indice ): restituisce la sotto-stringa della stringa di cui viene invocato il metodo a partire dall’indice corrispondente al parametro indice; substring( int primoIndice,int secondoIndice ): restituisce la sotto-stringa della stringa di cui viene invocato il metodo che inizia dall’indice definito dal parametro primoIndice e termina alla posizione precedente quella indicata dall’indice definito dal parametro secondoIndice; Programmazione ad oggetti – p.28/82 Metodi della classe String indexOf( String stringa ): restituisce l’indice della prima occorrenza della stringa definita dal parametro stringa nella stringa di cui viene invocato il metodo; indexOf( String stringa,int indice ): restituisce l’indice della prima occorrenza della stringa definita dal parametro stringa nella stringa di cui viene invocato il metodo cercando a partire dall’indice corrispondente al parametro indice; lastIndexOf( String stringa ): restituisce l’ultima posizione della stringa definita dal parametro stringa nella stringa di cui viene invocato il metodo. Programmazione ad oggetti – p.29/82 Uso di stringhe B enuti al paradiso dei linguaggi di programmazione! nvenuti 10 14 47 -1 Programmazione ad oggetti – p.30/82 Concatenazione L’operatore di concatenazione di stringhe è +. String primoMessaggio = "Buongiorno!"; String secondoMessaggio = "Mi chiamo Mario Rossi."; String messaggioTotale; messaggioTotale = primoMessaggio+secondoMessaggio; Per separare correttamente le due stringhe bisogna usare una terza stringa. messaggioTotale = primoMessaggio+" "+secondoMessaggio; Ad una stringa si possono concatenare anche oggetti di altra natura (Java si occupa della conversione). String soluzione = "La risposta e’ "+42; Il risultato è sempre un oggetto di tipo String. Programmazione ad oggetti – p.31/82 Sequenze escape Carattere \ (backslash) seguito da altro carattere. \": doppio apice. \’: apice singolo. \\: carattere backslash. \n: per andare a capo nella stringa. \r per andare all’inizio della riga corrente. \t per andare alla successiva tabulazione. Il carattere \ fa assumere al carattere che lo segue un significato speciale. Programmazione ad oggetti – p.32/82 Array in Java Gli array sono oggetti: non esiste un vero nome di classe, in quanto sono identificati dall’operatore []. Definire un array Java significa definire un riferimento: l’array vero e proprio va creato con new int[] a = new int[4] a meno che non sia specificato tramite una costante int[] a = {1,2,3,4}. Creare un array con new significa creare N celle di un tipo primitivo, se l’array è di valori primitivi N riferimenti a oggetti (tutti null) se l’array è di oggetti (i singoli oggetti dovranno essere creati esplicitamente con new). Ogg[] Q = new Ogg[4]; //alloca 4 rifer. ad oggetti di classe Ogg Q[0] = new Ogg(); Q[1] = new Ogg( param1, pram2 ); ... Programmazione ad oggetti – p.33/82 Librerie & Documentazione Java La libreria Java ha centinaia di classi e migliaia di metodi. La libreria java.lang è importata automaticamente. Una volta installata, la documentazione può essere consultata aprendo con un browser il file index.html. È in formato HTML, lo stesso generato da javadoc (strumento con cui i programmatori documentano ogni classe, metodo, parametro ed valore di ritorno). Contiene informazioni generali relative al linguaggio di programmazione, specifiche di progettazione e funzionali, manuali utenti, tutorial e programmi dimostrativi. Contiene inoltre la specifica della così detta application programming interface (API), ovvero la descrizione di tutte le classi ed i metodi contenuti nella libreria standard distribuita con l’ambiente di sviluppo. Programmazione ad oggetti – p.34/82 Alcune librerie standard java.lang: classi base del linguaggio Object, Thread, System, String, Math, Throwable, . . . java.io: classi di I/O FileInputStream, FileOutputStream, . . . java.utils: classi di utilità Date, Random, . . . java.net: classi di supporto per le applicazioni di rete Socket, URL, . . . java.awt: abstract window toolkit ... Programmazione ad oggetti – p.35/82 Firma di un metodo Firma di un metodo: nome del metodo, numero e tipo dei suoi parametri (il tipo del valore di ritorno non fa parte della firma). All’interno di una classe, i metodi devono avere tutti firma diversa. Programmazione ad oggetti – p.36/82 Sovraccaricamento di metodi I metodi presenti più volte con lo stesso nome si dicono sovraccaricati (i costruttori di solito lo sono). Quando un metodo sovraccaricato è invocato, l’interprete Java determina quale definizione usare in base a numero e tipo degli argomenti. Se la corrispondenza non esiste, Java cercherà di effettuare una qualche conversione di tipo. Se anche dopo la conversione non c’è corrispondenza, viene segnalato un errore. Programmazione ad oggetti – p.37/82 Sovraccaricamento di metodi La conversione automatica può portare a situazioni di ambiguità quando si hanno due definizioni di metodo che differiscono solo per l’ordine degli argomenti. int mioMetodo( int i,double j ) int mioMetodo( double j,int i ) Queste due definizioni non generano errori di compilazione, ma un’invocazione del tipo mioMetodo( 5,10 ) produrrà un messaggio di errore in fase di esecuzione, perché non è possibile decidere quale definizione scegliere: l’interprete, infatti, non sa se deve convertire il valore 10 di tipo int in double ed usare la prima definizione oppure convertire il valore 5 di tipo int in double ed usare la seconda definizione. Programmazione ad oggetti – p.38/82 Ereditarietà È una metodologia di organizzazione delle classi che facilita il riuso e la manutenzione del software Permette di definire una super-classe, e successivamente di definirne un’altra più specializzata, detta sotto-classe (o classe derivata), aggiungendo solo il necessario. class nome Sotto extends nome Super blocco Risparmio di lavoro: sotto-classe eredita proprietà di super-classe, necessario programmare solo nuove funzionalità. Sotto-classe utilizza campi e metodi di super-classe (oltre a quelli aggiunti nella sua definizione). In sotto-classe, metodi di super-classe possono essere opportunamente ri-definiti. Programmazione ad oggetti – p.39/82 Gerarchia delle classi Indica le relazioni di ereditarietà. Rappresenta graficamente il processo di specializzazione. Programmazione ad oggetti – p.40/82 Gerarchia delle classi Vediamo come avviene la dichiarazione di due delle classi derivate dalla classe FormaBidimensionale tramite la parola chiave extends. Programmazione ad oggetti – p.41/82 Ereditarietà e costruttori I costruttori non sono ereditati perché sono specifici della classe in cui sono definiti. Per costruire un oggetto di una classe derivata, è sempre necessario chiamare un costruttore della classe-base, in quanto: solo il costruttore della classe base può sapere come inizializzare i campi-dati ereditati da tale classe in modo corretto è il solo modo per garantire l’inizializzazione di campi-dati ‘privati’ (a cui la sottoclasse non può accedere direttamente) si evita una inutile duplicazione di codice nella sottoclasse. Programmazione ad oggetti – p.42/82 Ereditarietà e costruttori Il costruttore della sotto-classe deve, per prima cosa, invocare il costruttore della super-classe mediante la parola chiave super (usata al posto del nome del costruttore della classe padre). Se non viene esplicitamente inclusa questa invocazione al costruttore della classe padre come prima linea del corpo del costruttore della classe figlia, il compilatore Java automaticamente includerà come prima azione l’invocazione del costruttore di default della classe padre (ovvero super( )). Se la sotto-classe non ha costruttori espliciti, il costruttore di default invoca super( ). In entrambi i casi, si ha un errore se la super-classe non ha un costruttore senza parametri. Programmazione ad oggetti – p.43/82 Forme bidimensionali e costruttori Programmazione ad oggetti – p.44/82 Ereditarietà e campi Una sotto-classe può definire nuovi campi. Programmazione ad oggetti – p.45/82 Ereditarietà e metodi Una sotto-classe può definire nuovi metodi. Programmazione ad oggetti – p.46/82 super & this super è un riferimento alla classe base. this è un riferimento all’oggetto corrente (che può essere un parametro implicito di una invocazione di metodo). Sono espressioni lecite: this.val: indica il campo val della classe dell’oggetto corrente this.f(): richiama il metodo f() della classe dell’oggetto corrente this(...): (in un costruttore) richiama un costruttore della stessa classe super(...): richiama un costruttore della classe base super.val: indica il campo val della classe base super.f(): richiama il metodo f() della classe base Es. la sottoclasse definisce un campo o un metodo omonimo ad uno della classe base. Programmazione ad oggetti – p.47/82 super & this Supponiamo che la classe Counter definisca un campo val ed un metodo inc class DerivedCounter extends Counter { double val=super.val; // super.val indica il campo val di Counter public void inc( int val ) { super.inc(); this.val=this.val+val; // super.inc() chiama il metodo inc() di Counter // this.val indica il campo val di DerivedCounter } } Programmazione ad oggetti – p.48/82 Ereditarietà e sovrascrittura Una sotto-classe può sovrascrivere metodi della super-classe. Ciò accade quando un metodo della sotto-classe ha la stessa firma e tipo di ritorno di un metodo della super-classe. Per gli oggetti della sotto-classe, viene usato il metodo definito all’interno della sotto-classe. All’interno della sotto-classe, se si vuole invocare un metodo della super-classe, bisogna usare la parola chiave super seguita dal punto e dal nome del metodo da invocare. Programmazione ad oggetti – p.49/82 Gerarchia di forme bidimensionali Programmazione ad oggetti – p.50/82 Ereditarietà e tipi di dato Un oggetto può avere più di un tipo: un oggetto di una classe derivata è del tipo della sotto-classe e del tipo della super-classe (e di tutte le sue super-classi). Possibile usare un oggetto di una classe derivata in tutte le circostanze in cui è ammesso usare un oggetto della super-classe (il contrario non è possibile). Rettangolo mioRettangolo; Quadrato mioQuadrato = new Quadrato( 3 ); mioRettangolo = mioQuadrato; Non comporta perdita d’informazione perché si tratta di un assegnamento tra riferimenti. Problema: mioRettangolo è definito come riferimento ad un generico Rettangolo ma fa in effetti riferimento ad un Quadrato! Programmazione ad oggetti – p.51/82 Ereditarietà e tipi di dato Problema: mioRettangolo è definito come riferimento ad un generico Rettangolo ma fa in effetti riferimento ad un Quadrato! Se si invoca mioRettangolo.perimetro, visto che sia Rettangolo che Quadrato definiscono un metodo perimetro, quale dei due verrà eseguito? Programmazione ad oggetti – p.52/82 Polimorfismo Una funzione si dice polimorfa se è capace di operare su oggetti di tipo diverso specializzando il suo comportamento in base al tipo dell’oggetto su cui opera. I linguaggi a oggetti hanno intrinseco il polimorfismo, in quanto possono esistere - in classi diverse - funzioni con lo stesso nome ma con effetti completamente diversi. L’esempio precedente è un caso di polimorfismo verticale: quando si invoca mioRettangolo.perimetro sarà eseguito il metodo perimetro della classe Quadrato. Java, infatti, usa il late binding: cioè prevale il tipo effettivo dell’oggetto corrente (anzicché il tipo formale del riferimento). Programmazione ad oggetti – p.53/82 Organizzazione delle classi Le classi sono organizzate gerarchicamente: la classe Object è l’antenata di tutte le classi; se una classe non è dichiarata esplicitamente come classe derivata, Java assume che sia derivata da Object. Esiste una corrispondenza ben precisa fra nomi delle classi e nomi di file: in un file sorgente ci può essere un’unica classe ‘pubblica’, che deve avere lo stesso nome del file (case-sensitive!). Programmazione ad oggetti – p.54/82 Modificatori Parole chiave che possono essere anteposte alla dichiarazione di una classe, di un campo o di un metodo. Ne descriveremo 4, anche se Java ne include diversi altri. public e private influiscono sulle modalità di accesso alla componente modificata. static e final influiscono sul comportamento della componente modificata. Programmazione ad oggetti – p.55/82 Modificatori public: indica che non ci sono restrizioni su chi può utilizzare la classe, il campo oppure il metodo. private: rende il campo oppure il metodo non accessibile fuori dalla classe in cui è dichiarato. Permette incapsulamento. static: indica metodi e campi che appartengono a tutta la classe e che non richiedono l’instanziazione di alcun oggetto per poter essere utilizzati. Esempio: il metodo main. Utili per avere un solo esemplare della variabile o del metodo, indipendentemente da quante istanze sono state create (es. variabile contataore delle istanze). final: indica metodi, campi e classi che non possono essere sovrascritti o modificati o derivati. Programmazione ad oggetti – p.56/82 Incapsulamento Principio: lo stato interno di un oggetto può essere letto e modificato solo invocando i suoi metodi (cioè le variabili di istanza sono accessibili solo all’implementazione della classe). In pratica, oltre alla scrittura di commenti opportuni sul comportamento della classe e dei metodi messi a disposizione, si tratta di: dichiarare tutte le variabili di istanza come private; fornire dei metodi public accessori e mutatori per leggere o cambiare i dati dell’oggetto e per qualsiasi altra azione basilare come l’input e l’output; rendere private qualsiasi metodo che sia esclusivamente di supporto a quelli public. Esempio: Implementazione di una pila con push e pop. Programmazione ad oggetti – p.57/82 Modificatori di comportamento Una variabile static è una variabile che contiene informazioni valide per tutta la classe e non per qualche oggetto solamente (variabile di classe). Un metodo static è un metodo che può essere invocato senza creare alcun oggetto (metodo di classe). Ciò è essenziale per il metodo main. Poiché i membri static di una classe esistono indipendentemente dagli oggetti della classe, ad essi si può accedere indifferentemente con il nome della classe seguito da un punto e dal nome del metodo o della variabile, oppure con il nome di un oggetto istanza della classe seguito da un punto e dal nome del metodo o della variabile. Dei due modi, è preferibile il primo. Programmazione ad oggetti – p.58/82 Esempio: System.out La classe System fa parte della libreria java.lang, sempre automaticamente importata in ogni unità di compilazione, e rappresenta ‘il sistema sottostante’ (qualunque esso sia). out è un campo static (quindi non ha bisogno di essere creato, lo si può utilizzare direttamente) e rappresenta il dispositivo standard di uscita. Il comportamento di out è determinato dal suo tipo PrintStream: tale classe definisce diversi metodi, tra cui print e println. Programmazione ad oggetti – p.59/82 Esempio: Math La classe Math è una classe di sistema (definita in java.lang) che comprende solo metodi e variabili static: in pratica, è la libreria matematica. variabili di classe: E e PI metodi di classe: abs(), asin(), acos(), atan(), min(), max(), exp(), log(), pow(), sin(), cos(), tan(), sqrt(), etc. Programmazione ad oggetti – p.60/82 Esempio: il gioco dell’oca Programmazione ad oggetti – p.61/82 Modificatori di comportamento Il valore di una variabile final non può essere modificato (sostanzialmente, final permette di dichiarare costanti). Un metodo final non può essere sovrascritto con un nuovo metodo in una classe derivata. Un metodo dichiarato private è implicitamente final, perché non è accessibile al di fuori della sua classe, neanche da una classe derivata. Al contrario, una variabile di istanza private può essere accessibile indirettamente se esistono dei metodi accessori o mutatori. Se un metodo è dichiarato static, allora esso è implicitamente un metodo final. Se un’intera classe è dichiarata final, allora non può essere usata come super-classe, perché tutti i suoi metodi sono implicitamente final e, quindi, non sovrascrivibili. Programmazione ad oggetti – p.62/82 Tipi di variabili Variabili di istanza: appartengono ad un oggetto. Variabili dichiarate static: appartengono ad una classe. Variabili locali: appartengono ad un metodo. Variabili parametro: appartengono ad un metodo. Programmazione ad oggetti – p.63/82 Eccezioni Strumento per il trattamento degli errori. Spesso i programmi contengono istruzioni ‘critiche’, che potrebbero generare errori. Anziché tentare di prevedere i casi che possono generare errore, si esegue l’operazione in un blocco ‘controllato’. Se si produce un errore, l’operazione solleva un’eccezione. L’eccezione viene catturata dal blocco entro cui l’operazione era eseguita, e può essere gestita nel modo più appropriato. Una eccezione non catturata si propaga verso l’esterno, di blocco in blocco: se raggiunge il main, provoca la terminazione immediata del programma. Le eccezioni possono essere segnalate dal sistema (es. ArithmeticException, NullPointerException, . . . ) o dal programma (istruzione throw). Programmazione ad oggetti – p.64/82 Eccezioni Un’eccezione è un’istanza di una classe che, generalmente, estende Exception che fornisce un campo di tipo String per descrivere l’eccezione un metodo getMessage() per leggerne il contenuto. Un metodo che rileva un errore genera un’eccezione. L’eccezione deve essere catturata dal codice che ha chiamato il metodo. Altrimenti sarà gestita dal gestore di default (in genere, con un messaggio all’utente). Programmazione ad oggetti – p.65/82 Generare eccezioni Mediante l’istruzione throw che richiede come parametro un oggetto che rappresenta una eccezione. throw oggetto Eccezione ; La dichiarazione di un metodo che può generare delle eccezioni durante la sua esecuzione deve includere la clausola throws seguita dalla lista di tali eccezioni. tipo nome ( lista Par ) throws lista Ecc Esempio (metodo costruttore) Razionale(int n,int d) throws EccezioneRazionale { if ((n>0)&&(d>0)) { num = n; den = d; } else { throw new EccezioneRazionale( "N o D non positivo" ); } } Programmazione ad oggetti – p.66/82 Esempio: generare eccezioni Programmazione ad oggetti – p.67/82 Catturare eccezioni Un metodo che invoca un altro metodo che può generare un’eccezione, può comportarsi in tre modi differenti: può catturare l’eccezione e gestirla; può catturare l’eccezione e trasformarla in una nuova di un tipo incluso nella sua clausola throws; può dichiarare l’eccezione nella sua clausola throws e lasciarla quindi passare. La cattura di un’eccezione avviene tramite l’istruzione try-catch. try blocco catch (tipo Eccezione 1 nome Eccezione 1 ) blocco 1 ... catch (tipo Eccezione n nome Eccezione n ) blocco n finally blocco Finale Programmazione ad oggetti – p.68/82 Catturare eccezioni Il corpo della clausola try viene eseguito fin quando o termina con successo o viene generata un’eccezione. Quando viene generata un’eccezione, ogni clausola catch viene sequenzialmente esaminata alla ricerca di una clausala compatibile con il tipo dell’eccezione. Se c’è una clausola compatibile, il suo blocco di istruzioni viene eseguito e nessun’altra clausola catch viene esaminata. Altrimenti, viene generato un errore in fase di compilazione (a meno che il metodo invocante nella sua dichiarazione non abbia incluso la clausola throws corrispondente). Se la clausola finally è presente, il suo blocco di istruzioni viene comunque eseguito (indipendentemente dal fatto che un’eccezione sia stata generata o meno) alla fine di tutto il processo appena descritto. Programmazione ad oggetti – p.69/82 Esempio: catturare eccezioni Programmazione ad oggetti – p.70/82 Esempio: lettura da input Una tipica operazione ‘critica’ è la lettura da input. Il dispositivo di input standard è la variabile (static) System.in, di classe InputStream (una classe astratta, di cui System.in è una istanza ‘anomala’ predefinita). Poiché InputStream fornisce solo un metodo read() che legge singoli byte, si usa incapsulare System.in in un oggetto dotato di maggiori funzionalità, come ad esempio un BufferedReader, che fornisce anche un metodo readLine(). readLine() solleva una IOException in caso di errore in fase di input, mentre Integer.parseInt() solleva una NumberFormatException se la stringa restituita da readLine() non corrisponde alla sintassi di un numero intero. Programmazione ad oggetti – p.71/82 Esempio: lettura da input import java.io.*; class EsempioIn { public static void main(String args[]){ int a = 0, b = 0; InputStreamReader isr = new InputStreamReader(System.in); \\ modella una tastiera che legge un flusso di caratteri BufferedReader in = new BufferedReader(isr); \\ modella una tastiera che legge le linee come String try { System.out.print("Primo valore: "); a = Integer.parseInt(in.readLine()); System.out.print("Secondo valore:"); b = Integer.parseInt(in.readLine()); } catch (IOException e) { System.out.println("Errore in input"); } System.out.println("La somma vale " + (a+b)); } } Programmazione ad oggetti – p.72/82 Unità di compilazione Ogni unità di compilazione deve avere estensione .java. Ogni unità di compilazione deve avere una clase public con lo stesso nome del file. In un’unità di compilazione ci deve essere una sola classe public (le altre sono di supporto alla classe public e non sono visibili all’esterno). La compilazione produce tanti file .class quante sono le classi all’interno dell’unità di compilazione. Programmazione ad oggetti – p.73/82 Package Consentono di raggruppare classi che realizzano funzioni collegate tra di loro. Ogni classe fa parte di un package. Una classe può esplicitamente dichiarare di far parte di un package tramite l’istruzione package nome Package ; posta all’inizio dell’unità di compilazione di cui fa parte. Se manca l’istruzione package si intende che la classe fa parte di un package di default privo di nome che fa riferimento alle classi contenute nella directory corrente. Programmazione ad oggetti – p.74/82 Nomi di package e fyle system Il nome di un package è generalmente strutturato in più sequenze di caratteri separate da un punto. Es. piluc.gj.poligoni Esiste una corrispondenza biunivoca fra nome di un package e posizione nel file system delle classi del package. Es. Le classi del package piluc.gj.poligoni si trovano all’interno della directory piluc/gj/poligoni Il nome completo di una classe è il nome della classe preceduto dal nome del package concatenato con un punto. Es. piluc.gj.poligoni.Rettangolo Programmazione ad oggetti – p.75/82 Nomi di package e fyle system Il nome di un package è utilizzato per localizzare i file binari contenenti il byte code delle specifiche di interfaccia e delle classi. L’interprete java sa dove cercare le classi di sistema. Per default, le classi utente si trovano nella directory corrente. La variabile di ambiente CLASSPATH specifica una lista di directory, file .zip o file .jar in cui le classi utente si possono trovare. L’interprete cerca prima nel CLASSPATH e poi nelle directory delle classi di sistema. Esempio: CLASSPATH=C:/user/java/;C:/piluc/ Si cercano le classi a partire da quelle due directory. Quindi la classe C:/piluc/gj/poligoni/Rettangolo.class si trova indicandola col nome composto gj.poligoni.Rettangolo. Programmazione ad oggetti – p.76/82 Compilazione ed esecuzione Per compilare una classe che appartiene a un package bisogna compilare obbligatoriamente dalla directory di livello superiore, altrimenti non si generano le informazioni relative alla posizione nel file system. Es. javac poligoni/Curva.java Anche per eseguire una classe che appartiene a un certo package bisogna andare obbligatoriamente alla directory di livello superiore e invocare la classe con il suo nome ‘completo’: Es. java poligoni.Curva Programmazione ad oggetti – p.77/82 Uso di classi di altri package Per aggiungere funzionalità, un’applicazione può importare classi definite in altri package tramite l’istruzione import. import nome Package.nome Classe ; import nome Package.*; Es. import piluc.gj.poligoni.Curva e import piluc.gj.poligoni.*. Alternativamente, si può scrivere il nome completo della classe da importare (in funzione di CLASSPATH). Es. piluc.gj.poligoni.Curva riferisce la classe Curva del package piluc.gj.poligoni. Programmazione ad oggetti – p.78/82 Interfacce Tipo di dato che consiste di metodi dichiarati (e non definiti) e di costanti. Delega l’implementazione dei metodi alle sue realizzazioni. Una classe può implementare una o più interfacce definendo tutti i metodi dichiarati in queste ultime. Il compilatore produrrà un errore: se una classe che implementa un’interfaccia (non è dichiarata abstract e) non definisce il corpo di tutti i metodi dichiarati nell’interfaccia, o se si tenta di creare un’istanza di un’interfaccia usando new (non ha senso definire costruttori per un’interfaccia). Programmazione ad oggetti – p.79/82 Interfacce Sintassi interface nome Interfaccia blocco class nome extends classe implements lista Interfacce blocco Esempio public interface Curva { public boolean contiene( Point p ); } public classe CurvaVuota implements Curva { public boolean contiene( Point p ) { return false; } } Programmazione ad oggetti – p.80/82 Interfacce Un’interfaccia è uno strumento di pura progettazione, una classe è una combinazione di progettazione ed implementazione. Principale vantaggio: un programma scritto in modo che faccia riferimento all’interfaccia di una classe e ad una specifica implementazione potrà essere riutilizzato anche se l’implementazione dell’interfaccia viene modificata. Programmazione ad oggetti – p.81/82 Classi abstract Sono classi che non sono destinate alla creazione di istanze, ma fungono da superclasse per altre classi. Solo classi abstract possono contenere metodi abstract, cioè metodi non implementati (solo firma e tipo del valore di ritorno, senza corpo). Le classi astratte con metodi astratti forzano le sottoclassi ad implementare i metodi abstract; altrimenti, anche la sottoclasse è astratta e quindi non instanziabile. Programmazione ad oggetti – p.82/82