Eccezioni Dott. Ing. Leonardo Rigutini Dipartimento Ingegneria dellInformazione Università di Siena Via Roma 56 – – SIENA Uff
Eccezioni
Gestione errori Un fallimento nel programma può avere diverse cause: Dati errati in ingresso Errori di programmazione Ecc … Due aspetti nella gestione dei fallimenti: Individuazione : rilevare un fallimento Ripristino : ripristinare il programma in modo che possa continuare (gestire lerrore) Il problema maggiore nella gestione degli errori è che solitamente il punto in cui viene individuato lerrore non è accoppiato al punto di ripristino, cioè il punto dove viene gestito lerrore
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 allutente ? Terminare bruscamente il programma ? Il più delle volte quindi, lerrore verificatosi dentro un metodo deve essere riportato allesterno del metodo stesso: Ritornando un valore particolare (per esempio false, -1) Lanciando una ecezione
Ritorno di un valore particolare E possibile notificare lavvento 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: Es. il metodo drive(int km) di Auto, ritorna 0 se è finita la benzina, > 0 altrimenti. I problemi di questo approccio : 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 dallerrore: 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 Lalternativa alluso di valori di ritorno delle funzioni, è di lanciare particolari eventi che segnalano in maniera asincrona il verificarsi dellerrore: Le eccezioni Le eccezioni sono classi particolari di Java che memorizzano lerrore e vengono lanciate e catturate allinterno del programma: Lanciare un eccezione quando avviene un errore Catturare leccezione per gestire lerrore 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 UnknownHostException MalformedURLException FileNotFoundException EOFException 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 { } 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; } variabile E memorizza un oggetto eccezione di tipo noGasException
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 lerrore 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. Lesecuzione non procede nel metodo che aveva invocato quella funzione, ma nel gestore delleccezione. 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 leccezione 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 laccesso 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 leccezione 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 leccezione e gestirla in locale Rimandare leccezione 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 leccezione al chiamante Es class noGasException extends Exception { } 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; } Ogni metodo che chiama drive() deve gestire leccezione di tipo noGasException
Rimandare leccezione 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()) { carburante = 0; } carburante=-c; } Il metodo checkGas lancia un eccezione noGasException e deve essere gestita in drive(): drive rimanda la gestione alla funzione che chiamerà drive()
Rimandare leccezione 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 leccezione 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 dellambiente 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 delleccezione E1 } catch (eccezione_tipo2 E2) { linee di codice per la gestione delleccezione 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); } carburante=-c; return carburante; } class Autodromo { public void run() { Auto Ferrari=new Auto(); try { Ferrari.drive(200); Ferrari.stop(); } catch (noGasException E) { System.err.println(E finita la benzina!!); } La clausola throws noGasException non è più necessaria, poiché leccezione è gestita localmente nel try - catch Se il metodo drive lancia un eccezione noGasException essa viene catturata dal catch
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 leccezione. 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(); } catch (noGasException E) { } Come sappiamo che è 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(); try { Ferrari.drive(200); Ferrari.stop(); } catch (noGasException E) { E.printStackTrace(); } Stampa lo stack della applicazione sullo stderr. Notare luso della variabile noGasException E e la chiamata ad un metodo 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 leccezione.
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; } class Autodromo { public void run() { Auto Ferrari=new Auto(); try { Ferrari.drive(200); Ferrari.stop(); } finally { System.out.println(Ferrari ciao!); } La stampa di Ferrari ciao!! viene sempre eseguita, sia che avvenga leccezione sia che non avvenga.
Note di cronaca Il 4 giugno 1996, il razzo Arianne sviluppato dallESA 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 leccezione
Note di cronaca Quando avvenne il trabocco, venne lanciata leccezione e poiché non cera 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 !!