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