Eccezioni
Dott. Ing. Leonardo Rigutini
Dipartimento Ingegneria dell’Informazione
Università di Siena
Via Roma 56 – 53100 – SIENA
Uff. 0577233606
[email protected]
www.dii.unisi.it/~rigutini/
Eccezioni
Gestione errori

Un fallimento nel programma può avere diverse cause:




Due aspetti nella gestione dei fallimenti:



Dati errati in ingresso
Errori di programmazione
Ecc …
Individuazione : rilevare un fallimento
Ripristino : ripristinare il programma in modo che possa continuare
(gestire l’errore)
Il problema maggiore nella gestione degli errori è che solitamente il
punto in cui viene individuato l’errore non è accoppiato al punto di
ripristino, cioè il punto dove viene gestito l’errore
Gestione errori

Per esempio:


se nella classe Integer utilizziamo il metodo parseInt per trasformare una
String in un int, ma la stringa che attualmente stiamo esaminando non
rappresenta un numero (es. “pippo”), abbiamo un errore (a run-time) ma
il metodo parseInt non è in grado di decidere cosa fare: chiedere
all’utente ? Terminare bruscamente il programma ?
Il più delle volte quindi, l’errore verificatosi dentro un metodo deve
essere riportato all’esterno del metodo stesso:


Ritornando un valore particolare (per esempio false , -1)
Lanciando una ecezione
Ritorno di un valore particolare

E’ possibile notificare l’avvento di un errore dentro una funzione
facendo in modo che tale funzione ritorni un valore se tutto viene
eseguito in maniera corretta, un altro altrimenti:


I problemi di questo approccio :



Es.
il metodo drive(int km) di Auto, ritorna 0 se è finita la benzina, > 0
altrimenti.
diventa necessario ogni volta controllare i valori di ritorno delle funzioni per
verificare se sono occorsi errori o meno.
In ogni funzione è necessario controllare che ogni istruzione (o quasi) sia
eseguita in maniera corretta
Inoltre ritornando un tipo di dato semplice (int o boolean) nessuna
informazione può essere estratta dall’errore:

La cosa da fare sarebbe di creare una classe apposita che sia ritornata da
ogni metodo e che memorizzi le informazioni sugli errori
Lanciare un eccezione

L’alternativa all’uso di valori di ritorno delle funzioni, è di lanciare
particolari eventi che segnalano in maniera asincrona il verificarsi
dell’errore:


Le eccezioni sono classi particolari di Java che memorizzano
l’errore e vengono lanciate e catturate all’interno del programma:



Le eccezioni
Lanciare un eccezione quando avviene un errore
Catturare l’eccezione per gestire l’errore
Per lanciare una eccezione si usa la parola riservata throw
Eccezioni

Il java fornisce una serie di classi built-in per gestire le eccezioni,
tutte derivate dalla classe Exception:
Exception
IOException
EOFException
FileNotFoundException
MalformedURLException
UnknownHostException
ClassNot
Found
Exception
CloneNot
Supported
Exception
RuntimeException
Aritmetic
Exception
ClassCast
Exception
NullPointer
Exception
…
Es.

Supponiamo di progettare la funzione drive() della classe Auto in
modo che notifichi il fatto che vogliamo percorrere più km di quanti
ne possiamo fare con la benzina rimasta nel serbatoio
class Auto {
//
public void drive(double km) {
double c= km/consumo;
if (c > carburante) throw new Exception(“Not enough gas”);
carburante=-c;
}
}
Lancia una eccezione
generica Exception
Eccezioni

In alternativa è possibile ovviamente derivare classi per le eccezioni
che riguardano il nostro progetto

Per esempio noGasException:
class noGasException extends Exception {
}
class Auto {
//
public void drive(double km) {
double c= km/consumo;
if (c > carburante)
throw new noGasException (“Not enough gas”);
carburante=-c;
}
}
Oggetti Exception

Essendo una classe, ogni eccezione può essere memorizzata in
una variabile oggetto:
class noGasException extends Exception {
}
variabile E  memorizza
un oggetto eccezione di tipo
noGasException
class Auto {
//
public void drive(double km) {
double c= km/consumo;
noGasException E;
if (c > carburante) {
E=new noGasException (“Not more gas”);
carburante=0;
throw E;
}
carburante=-c;
}
}
Classe Error

Esiste una seconda categoria di errori interni che vengono segnalati
lanciando oggetti di tipo Error.

Questi errori sono errori fatali che accadono di rado e non sono
controllabili dal programmatore. Ad esempio l’errore
OutOfMemoryError, che viene lanciato quando non vi è più memoria
disponibile.

Tali situazioni non sono gestibili dal programmatore
Lanciare una eccezione


Quando viene lanciata una eccezione, il metodo termina
immediatamente la propria esecuzione, come se fosse stato
eseguito un return . L’esecuzione non procede nel metodo che
aveva invocato quella funzione, ma nel gestore dell’eccezione.
E’ buona regola non abusare del lancio di eccezioni: consideriamo
la funzione readLine della classe BufferedReader. Questa funzione
legge una riga (fino a \n) da un input stream. Questa funzione non
ritorna una eccezione di tipo EOFException quando arriva in fondo
allo stream. Perché?
La risposta è che terminare lo stream non è una cosa eccezionale,
cioè se si legge un file, è ovvio che prima o poi si arriva alla fine del
file e quindi trovare EOF non è da considerarsi errore
Eccezioni controllate e non-controllate




Le eccezioni Java rientrano in due categorie, chiamate eccezioni
controllate e non controllate
Eccezioni controllate – quando chiamate un metodo che lancia
una eccezione controllata, dovete specificare come gestire
l’eccezione nel caso essa venga lanciata. Ad esempio tutte le
eccezioni IOException sono eccezioni controllate.
Eccezioni non controllate – il compilatore non richiede che teniate
traccia delle eccezioni non controllate come NullPointerException,
NumberFormateException ecc …
Più in generale tutte le eccezioni che appartengono alle sottoclassi
di RuntimeException sono eccezioni non controllate, mentre tutte la
altre sottoclassi di Exception sono controllate.
Eccezioni controllate e non controllate

Perché ci sono due tipi di eccezioni?



Un eccezione controllata descrive un problema che prima o poi può
accadere, indipendentemente da quanto sia stato scritto bene il codice
Le eccezioni non controllate, invece, rappresentano un errore in
programmazione. Quindi diventa inutile forzare la gestione di una
eccezione di questo tipo, poiché nel caso si verifichi, è necessario
modificare il codice.
Ad esempio:


La fine inattesa di un file (EOFException) non dipende da cause che
sono sotto il nostro controllo (errore sul disco o errore di rete) e quindi si
è forzati a prevedere una routine di gestione di tali situazioni
Un nullPointerException, invece, segnala un accesso ad una variabile
non inizializzata (null) e quindi un errore nel codice che cerca di
utilizzare un riferimento null. Il compilatore non verifica che gestiate un
nullPointerException poiché dovreste scrivere codice che eviti l’accesso
a riferimenti null
Eccezioni controllate e non controllate

In realtà queste categorie non sono perfette:
non è colpa del programmatore se un utente inserisce un numero
non corretto, ma l’eccezione NumberFormatException, lanciata da
Integer.parseInt(String) è un eccezione non controllata

Vedrete che la maggior parte delle eccezioni controllate accodono
nella gestione dei dati in ingresso o in uscita, che è un fertile terreno
per guasti esterni che non dipendono dal codice:




Un file può essere stato rimosso o corretto
La rete può essere disattivata
Un server può essere non disponibile
Ecc …
Eccezioni controllate

Quando viene utilizzato un metodo che lancia una eccezione
controllata, è necessario specificare cosa fare:



Rimandare la gestione della eccezione al metodo chiamante
Catturare l’eccezione e gestirla in locale
Rimandare l’eccezione al chiamante – se stiamo implementando
un metodo in cui viene lanciata una eccezione controllata (o che
utilizza un metodo che lancia una eccezione controllata) e non
siamo in grado di gestirla, tale eccezione viene passata direttamente
al metodo chiamante. Questa operazione viene fatta post-ponendo
alla dichiarazione del metodo che stiamo implementando la parola
di codice throws
Rimandare l’eccezione al chiamante

Es
class noGasException extends Exception {
}
Ogni metodo che chiama
drive() deve gestire l’eccezione
di tipo noGasException
class Auto {
//
public void drive(double km) throws noGasException {
double c= km/consumo;
if (c > carburante) {
carburante = 0;
throw new noGasException (“Not enough gas”);
}
carburante=-c;
}
}
Rimandare l’eccezione al chiamante
class Auto {
public int checkGas() throws noGasException {
if (carburante <=0 ) {
throw new noGasException (“Not more gas”);
}
return carburante;
}
//
public void drive(double km) throws noGasException {
double c= km/consumo;
if (checkGas()) {
Il metodo checkGas lancia
carburante = 0;
un eccezione noGasException
}
e deve essere gestita in drive():
carburante=-c;
drive rimanda la gestione alla
}
funzione che chiamerà drive()
}
Rimandare l’eccezione al chiamante

Quando un metodo rimanda più eccezioni controllate, vanno
elencate dopo throws separate con virgole:
public void drive(double km) throws IOException,
noGasException {
…
checkGas() <- lancia noGasException
readFile() <- lancia IOException
…
}
Rimandare l’eccezione al chiamante

Ovviamente se viene fatto un throws di un tipo di eccezione, tutte i
tipi di eccezioni figli sono compresi:
public void drive(double km) throws Exception {
…
checkGas() <- lancia noGasException
readFile() <- lancia IOException
…
}
Tutte le eccezioni
Sono prese da
throws Exception
Catturare le eccezioni

Una eccezione prima o poi dovrà essere gestita. Normalmente
accade che le eccezioni vengono lanciate in classi di basso livello e
gestite nelle classi di alto livello, dove si ha una conoscenza
dell’ambiente maggiore

Per gestire una eccezione è necessario catturarla ed implementare
il codice che deve essere eseguito una volta catturata.

Se devono essere gestite due eccezioni, è necessario specificare il
codice per ogni tipo di eccezione che viene catturata oppure
catturare una eccezione padre di entrambe (ereditarietà).
Catturare le eccezioni

Per catturare le eccezioni si usa il costrutto:
try {
linee di codice del programma
}
catch (eccezione_tipo1 E1) {
linee di codice per la gestione dell’eccezione E1
}
catch (eccezione_tipo2 E2) {
linee di codice per la gestione dell’eccezione E2
}
…

Le istruzioni contenute nel blocco di codice delimitato da try { e } sono
eseguite come normale linee di codice. Appena avviene in esse una
eccezione (generata localmente o derivante da qualche funzione interna al
try ) , il controllo viene passato al ramo catch() relativo e vengono eseguite
le istruzioni delimitate da { }
Catturare le eccezioni
class Auto {
public int drive(double km) throws noGasException {
double c= km/consumo;
if (carburante <= c ) {
carburante = 0;
throw new noGasException (“No more gas”);
}
La clausola throws noGasException
carburante=-c;
return carburante;
non è più necessaria, poiché
}
l’eccezione è gestita localmente nel
}
try - catch
class Autodromo {
public void run() {
Auto Ferrari=new Auto();
Se il metodo drive lancia un
try {
eccezione noGasException
Ferrari.drive(200);
essa viene catturata dal catch
Ferrari.stop();
}
catch (noGasException E) {
System.err.println(“E’ finita la benzina!!”);
}
}
}
Informazioni sulle eccezioni

La classe Exception di Java prevede una serie di funzioni utiliìssime
in caso di debug.




Ha la possibilità di memorizzare un messaggio in fase di lancio della
eccezione (nel costruttore).
printStackTrace() – stampa lo stack della CPU al momento della
eccezione (lista di funzioni attualmente nello stack di sistema)
getMessage() – stampa il messaggio memorizzato dentro l’eccezione.
E’ consigliato gestire sempre in maniera esplicita le eccezioni,
eventualmente stampando sullo stdout il messaggio e lo sack:

non mettere a tacere le eccezioni per nessun motivo, poiché se si
verifica un errore non ce ne accorgiamo
Catturare le eccezioni
class Auto {
public int drive(double km) throws noGasException {
double c= km/consumo;
if (carburante <= c ) {
carburante = 0;
throw new noGasException (“Not more gas”);
}
carburante=-c;
return carburante;
}
}
class Autodromo {
public void run() {
Auto Ferrari=new Auto();
try {
Ferrari.drive(200);
Ferrari.stop();
}
Come sappiamo che è
catch (noGasException E) {
avvenuta una eccezione??
}
}
}
Catturare le eccezioni
class Auto {
public int drive(double km) throws noGasException {
double c= km/consumo;
if (carburante <= c ) {
carburante = 0;
throw new noGasException (“Not more gas”);
}
carburante=-c;
return carburante;
}
}
class Autodromo {
public void run() {
Auto Ferrari=new Auto();
Stampa lo stack della
try {
Ferrari.drive(200);
applicazione sullo stderr.
Ferrari.stop();
Notare l’uso della variabile
}
noGasException E e la
catch (noGasException E) {
chiamata ad un metodo
E.printStackTrace();
ereditato da Exception
}
}
}
La clausola finally

A volte si ha bisogno di eseguire comunque delle istruzioni prima di
lasciare il comando al gestore delle eccezioni.

Il costrutto finally { } permette di specificare una serie di istruzioni
che devono essere eseguite comunque sia che non si verifichi
nessuna eccezione, sia che si sia verificata una eccezione.

Un esempio classico è la chiusura di un file: se abbiamo aperto un
file e si verifica una eccezione (una delle tante che possono essere
catturate), è necessario chiudere il file prima di gestire l’eccezione.
La clausola finally
class Auto {
public int drive(double km) throws noGasException {
double c= km/consumo;
if (carburante <= c ) {
carburante = 0;
throw new noGasException (“Not more gas”);
}
carburante=-c;
return carburante;
}
}
La stampa di “Ferrari
class Autodromo {
ciao!!” viene sempre
public void run() {
eseguita, sia che avvenga
Auto Ferrari=new Auto();
l’eccezione sia che non
try {
avvenga.
Ferrari.drive(200);
Ferrari.stop();
}
finally {
System.out.println(“Ferrari ciao!”);
}
}
}
Note di cronaca




Il 4 giugno 1996, il razzo Arianne sviluppato dall’ESA virò dalla sua
rotta dopo circa 40 secondi dal lancio e dovette essere distrutto in
volo per evitare pericoli
La causa che innescò questo incidente fu un eccezione non gestita!
Il missile conteneva due sensori (uno di riserva) che elaboravano
dati e li trasformavano in informazioni riguardanti la posizione del
missile.
Uno dei sensori misurò una forza di accelerazione maggiore e tale
valore espresso in virgola mobile doveva essere memorizzato in un
intero a 16 bit.
Il linguaggio ADA, utilizzato nei dispositivi, genera una eccezione nel
caso di simili cast ma i programmatori avevano deciso che tale
situazione non sarebbe mai accaduta e non avevano gestito
l’eccezione
Note di cronaca





Quando avvenne il trabocco, venne lanciata l’eccezione e poiché
non c’era il gestore, il sensore si spense.
Il computer allora attivò il sensore di riserva, che lanciò la stessa
eccezione e si spense anche lui.
I progettisti non avevano previsto che due sensori si spegnessero
insieme dato che le probabilità di un simile evento sono
remotissime.
A quel punto il razzo era privo delle informazioni sulla propria
posizione e sulla rotta.
… BUM !!
Scarica

5. eccezioni