Eccezioni Introduzione Il termine eccezione è un'abbreviazione della frase " evento eccezionale"; esso può essere definito nel seguente modo: Un'ecc Un'e ccez ezio ione ne è un even evento to che che si veri verifi fica ca dura durant nte e l'es l'esec ecuz uzio ione ne di un programma (a runtime) e che interrompe il normale flusso di istruzioni. Generalizzando, possiamo classificare gli errori che generano eventi eccezionali, in quattro tipi: 1. 2. 3. 4.
errori di errori di input input dell' dell'ute utente nte;; errori errori dei dispositi dispositivi vi (ad es. file non esistent esistente, e, stampante stampante spenta, spenta, ecc.); ecc.); limitazio limitazioni ni fisiche fisiche (ad (ad es. memor memoria ia esaurit esaurita); a); errori di codice (ad es. elaborazione di un indice di array non valido).
Quando in un metodo si verifica un errore del tipo descritto, il metodo crea un oggetto particolare, detto oggetto eccezione o semplicemente semplicemente eccezione (exception ) e lascia al sistema runtime il compito di occuparsi di tale errore. In altre parole si dice che il metodo lancia un'eccezione (throwing an exception). L'oggetto eccezione contiene informazioni riguardanti l'errore e lo stato nel quale si trovava il programma quando l'errore si è verificato. L'errore originato da un metodo può essere gestito in diverse maniere: 1. Si può decidere di non occuparsi dell'errore. In tal caso, il programma viene terminato con una segnalazione dell'errore verificatosi. 2. Il metodo metodo può termina terminare re restitue restituendo ndo uno specia speciale le codice codice di error errore, e, che sarà analizzato dal chiamante. 3. L’errore può essere intercettato e gestito.
Per comprendere meglio ciò che accade, vediamo come viene gestito o non gestito un errore nei tre casi, mediante alcuni esempi.
Caso1: nessuna gestione dell’errore Supponiamo che un programmatore A fornisca la classe Vettore, una classe pubblica, non eseguibile, con un metodo statico la cui intestazione è:
public static int getElemento(int index)
Il metodo metodo getEle getEleme mento nto restit restituis uisce ce l'ele l'eleme mento nto che che si trova, trova, all'in all'inter terno no dell'a dell'arra rray, y, all'indice specificato come parametro.
Autore: Cinzia Bocchi Ultimo aggiornamento: 10/10/11
1
public class Vettore { static int int[] [] a= {0,1,2,3,4,5,6,7,8,9}; public static int getElemento(int getElemento(int index) { return a[index]; } }
Supponiamo che un programmatore B scriva un'applicazione che stampa l'elemento di un'array in una data posizione, utilizzando la classe fornita dal programmatore A. import java.util.*; public class UsaVettore { public static void main(String main(String[] [] args) { Scanner console= new Scanner(System Scanner(System.in); .in); int index, elemento; System.out.println( System .out.println("Inserire "Inserire l'indice dell'elemento da stampare"); stampare" ); index= console.nextInt(); elemento= Vettore.getElemento(index); System.out.println( System .out.println("elemento "elemento di indice " +index+ " = " +elemento); System.out.println( System .out.println("FINE "FINE ELABORAZIONE"); ELABORAZIONE"); } }
L'esecuzi L'esecuzione one del metodo metodo main può dare origine origine ad un errore, errore, nel caso in cui l'utente l'utente inserisca un indice maggiore di 9 o negativo. Nel caso in cui il valore inserito per l'indice sia 10, il programma p rogramma termina producendo in output la seguente segnalazione: Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10 at Vettore.getElemento Vettore.getElemento at UsaVettore.main
Il messaggio ci avverte che: durante l'esecuzione del metodo main si è verificata un'eccezione •
Exception in thread "main"; •
l'errore è stato determinato dal tentativo di accedere ad un elemento di indice 10, cioè al di fuori della dimensione dell'array ArrayIndexOutOfBoundsEx ArrayIndexOu tOfBoundsException ception : 10;
•
l'istruzione che ha causato l'eccezione si trova alla riga 6 del file Vettore.java e si è verificata durante l'esecuzione del metodo getElemento at Vettore.getElemento ;
•
l'eccezione si è propagata al metodo main, precisamente alla riga 11, della classe UsaVettore, contenuta all'interno del file UsaVettore.java at UsaVettore. UsaVettore.main main .
Se si decide di non gestire g estire l'eccezione, durante l'esecuzione dell'istruzione return a[index];
del metodo getElemento, viene lanciata un'eccezione di tipo ArrayIndexOutOfBoundsException
Autore: Cinzia Bocchi Ultimo aggiornamento: 10/10/11
2
L'ecc L'eccezi ezione one viene viene passat passata a al chiama chiamante nte (main), il qual quale, e, non non prev preved eden endo do alcu alcun n strume strumento nto di gestio gestione ne dell' dell'ecc eccezi ezione one,, termin termina a brusca bruscamen mente te con la segnal segnalazi azione one dell'errore riscontrato.
Caso 2: gestione dell’errore mediante codice di errore Supponiamo che un programmatore A, nello scrivere il metodo getElemento, segua le indicazioni del punto 2, controllando la validità dell'indice passato come parametro. public class Vettore { static int int[] [] a= {0,1,2,3,4,5,6,7,8,9}; public static int getElemento(int getElemento(int index) { final int ERRORE= -1; if (index>=0 (index>=0 && index<=9) { return a[index]; } else { return ERRORE; } } }
Il metodo getElemento restituisce -1 se l'indice non è valido. Il programma chiamante dovrà controllare il valore restituito per sapere se l'operazione è andata a buon fine o se si è verificato un errore. import java.util.*; class UsaVettore { public static void main(String main(String [] args) { Scanner console= new Scanner(System Scanner(System.in); .in); int index, elemento; System.out.println( System .out.println("Inserire "Inserire l'indice dell'elemento da stampare"); stampare" ); index= console.nextInt(); elemento= Vettore.getElemento(index); if (elemento!=-1) (elemento!=-1) { System.out.println( System .out.println("elemento "elemento di indice " +index+ " = " +elemento); System.out.println( System .out.println("FINE "FINE ELABORAZIONE"); ELABORAZIONE"); } else { System.out.println( System .out.println("ERRORE: "ERRORE: INDICE NON VALIDO"); VALIDO" ); } } }
L'output prodotto dal programma, nel caso in cui il valore inserito per l'indice sia 10, è: ERRORE: INDICE NON VALIDO Autore: Cinzia Bocchi Ultimo aggiornamento: 10/10/11
3
Si pone il seguente interrogativo:
Quale valore di ritorno è opportuno scegliere nel caso di indice di array non valido? Nel caso specifico, il valore -1 è adeguato perché l'array della classe Vettore contiene solo interi da 0 a 9 e quindi non si corre il rischio di confondere un elemento dell'array con un codice di errore. In altre situazioni il valore -1 potrebbe non essere opportuno. Per esempio se l'array contiene numeri interi, senza limitazioni, non è possibile capire se il valore restituito è effettivamente un elemento dell'array o un codice di errore. Inoltre, potete notare come il codice si appesantisca e divenga meno leggibile, a causa dei controlli (costrutti if) inseriti al suo interno. Vediamo allora come si può scrivere un codice migliore dei precedenti, attenendoci alle indicazioni del punto 3.
Caso 3: intercettare e gestire l’errore con try e catch public class Vettore { static int int[] [] a= {0,1,2,3,4,5,6,7,8,9}; public static int getElemento(int getElemento(int index) { return a[index]; } } import java.util.*; public class UsaVettore { public static void main main((String String[] [] args) { Scanner console= new Scanner(System Scanner(System.in); .in); int index, elemento; System.out.println( System .out.println("Inserire "Inserire l'indice dell'elemento da stampare"); stampare" ); index= console.nextInt(); try { elemento= Vettore.getElemento(index); System.out.println( System .out.println("elemento "elemento di indice " +index+ " = " +elemento); System.out.println( System .out.println("FINE "FINE ELABORAZIONE"); ELABORAZIONE"); } catch((ArrayIndexOutOfBoundsException e) catch { System.out.println( System .out.println("ERRORE: "ERRORE: INDICE NON VALIDO"); VALIDO" ); } } }
L'output prodotto dal programma, nel caso in cui il valore inserito per l'indice sia 10, è: ERRORE: INDICE NON VALIDO
Autore: Cinzia Bocchi Ultimo aggiornamento: 10/10/11
4
Le differenze rispetto al codice dell'esempio 2 sono: il metodo getElemento non deve preoccuparsi di restituire un codice di errore opportuno; il metodo chiamante ( main) può stabilire se l'operazione è andata a buon fine senz senza a dove doverr cont contro roll llar are e il valo valore re rest restit itui uito to dal dal me meto todo do getE getEle leme ment nto, o, ma semplicemente ins inseren erendo do il codi codice ce che può può gene genera rare re un un'e 'ecc ccez ezio ione ne all' all'in inte tern rno o di un particolare costrutto try try;; insere inserendo ndo il codice codice che che gestis gestisce ce l'ecc l'eccezi ezione one all'in all'inter terno no di un partic particola olare re costrutto catch catch..
−
−
Quando il metodo getElemento lancia l'eccezione ArrayIndexOutOfBoundsException, essa viene gestita dal chiamante attraverso il codice presente nel blocco catch. Se nessuna eccezione viene lanciata, il blocco catch è ignorato.
Recuperare l’errore Nell ell'ese 'esem mpio pio 3 abbi abbiam amo o visto isto come ome sia pos possibi sibile le ril rilev evar are e un un'e 'ec ccezio ezion ne e, conseguentemente, terminare il programma. Questo comportamento non sempre è gradito all'utente. Pensate, per esempio, ad una fase molto lunga di inserimento di dati, che viene interrotta bruscamente per un errore di digitazione: l'utente dovrà mandare in esecuzione il programma e inserire nuovamente tutti i dati. Dopo un po' di volt volte, e, cerc cerche herà rà di acqu acquis ista tare re un prod prodot otto to più più affi affida dabi bile le.. Di cons conseg egue uenz nza, a, ove ove possibile, è preferibile cercare di rimediare all'errore. Il codice seguente è una versione migliorata del programma dell'esempio 3. Esso comprende un ciclo while che permette all'utente di inserire più volte il dato di input, fino a quando diventa accettabile. import java.util.*; public class UsaVettore { public static void main main((String String[] [] args) { Scanner console= new Scanner(System Scanner(System.in); .in); int index, elemento; boolean b= true true;; while(b) while (b) { System.out.println( System .out.println("Inserire "Inserire l'indice dell'elemento da stampare" stampare"); ); index= console.nextInt(); try { elemento= Vettore.getElemento(index); System.out.println( System .out.println("elemento "elemento di indice " +index+ " = " +elemento); System.out.println( System .out.println("FINE "FINE ELABORAZIONE"); ELABORAZIONE"); b= false false;; //consente di uscire dal ciclo } catch((ArrayIndexOutOfBoundsException e) catch { System.out.println( System .out.println("ERRORE: "ERRORE: INDICE NON VALIDO"); VALIDO" ); } } } } Autore: Cinzia Bocchi Ultimo aggiornamento: 10/10/11
5
Tipi di eccezioni Le eccezioni, in Java, si distinguono distinguono in due gruppi: eccezioni controllate; eccezioni incontrollate.
Le eccezioni incontrollate sono causate generalmente da errori di codice o da errori interni (per esempio esempio l'esauri l'esaurimento mento della memoria) memoria).. Esempi Esempi di eccezion eccezionii incontrol incontrollate late dovute dovute ad errori errori di programmazione, programmazione, sono: cast difettoso; accesso ad array fuori dai limiti; accesso a puntatore nullo. − − −
Le eccezioni controllate si verifica verificano no principa principalmen lmente te durante durante l'access l'accesso o a risorse risorse periferic periferiche. he. Esempi di tali eccezioni sono: tentativo di leggere oltre la fine di un file; impossibilità impossibilità di trovare t rovare un file; tentativo di aprire un URL errato. − − −
Un'ulteriore Un'ulteriore differenza tra i due tipi di eccezioni si manifesta nel modo in cui devono essere gestite. Se un metodo lancia un'eccezione un'eccezione incontrollata, il programmatore è libero di scegliere se gestirla o meno. In altre parole il programmatore può inserire la chiamata al metodo in un costrutto try, oppure no, con le conseguenze che abbiamo visto negli esempi precedenti. Se un metodo lancia un'eccezione controllata, il programmatore è obbligato ad inserire la chiamata ad esso in un costrutto try, altrimenti il compilatore segnalerà l'errore. Per capire se un metodo lancia un'eccezione, occorre leggere la sua firma: dopo la lista dei parametri formali compare la parola chiave throws throws,, seguita dalla classe alla quale appartiene l'eccezione lanciata. public (parametri) throws Le classi di eccezioni sono sottoclassi della classe Throwable (sottoclasse diretta di Object) del package java.lang package java.lang.. Possiamo provare, per esempio, a modificare il metodo getElemento della classe Vettore affinchè lanci un’eccezione un’eccezione ArrayIndexOutOfBoundsExceptio ArrayIndexOutOfBoundsException. n. public class Vettore { static int int[] [] a= {0,1,2,3,4,5,6,7,8,9}; public static int getElemento(int getElemento(int index) throws ArrayIndexOutOfBoundsException { return a[index]; } }
Se decidiamo di non gestire l’errore con try e catch nel main, il compilatore non segnalerà alcun errore, in caso di inserimento di input errato, perché l'eccezione non è controllata. L’informazione dell’eccezione lanciata sarà invece utile al programmatore della classe usaVettore, che potrà predisporre adeguatamente il codice del chiamante.
Lanciare eccezioni Autore: Cinzia Bocchi Ultimo aggiornamento: 10/10/11
6
Consideriamo la seguente classe, che descrive un impiegato. public class Impiegato { private String nome; private double stipendio; public Impiegato(String Impiegato(String n, double s) { nome= n; setStipendio(s); } public void setStipendio(double setStipendio(double s) { if (s>0) (s>0) { stipendio= s; } else { System.out.println(" System .out.println("ERRORE: ERRORE: stipendio negativo"); negativo "); System.exit(1); System .exit(1); } } }
Se l'utente crea una istanza di Impiegato con stipendio negativo o nullo, il programma System.exit(1) .exit(1). E’ bene termi termina na a causa causa dell’i dell’ist struz ruzio ione ne System bene osse osserv rvar are e che che il me meto todo do setStipendio non lancia alcuna eccezione poiché l’inserimeno di un valore numerico negativo o nullo non rappresenta un errore di per sé. Tuttavia, l’errore esiste per la logica del programma poiché non ha senso attribuire ad un impiegato uno stipendio non positivo. La soluzione proposta è artificiosa e sgradita all’utente in quanto non è previsto alcun meccanismo per il recupero dell’errore: sarebbe sufficiente richiedere l’inserimento dello stipendio corretto. D’alt D’altro ro canto, canto, non è possib possibile ile utili utilizza zzare re try e catch catch perché perché il metodo metodo non lancia lancia eccezioni di nessun tipo. L’unica soluzione consiste, allora, nel forzare il metodo a lanciare un’ eccezione e lasciare la sua gestione al chiamante. La procedura da seguire è riassunta in 3 punti: 1. cercare la classe di eccezione adatta allos copo, se esiste, o crearne una exnovo derivandola da una classe esistente; 2. inserire nella firma del metodo la clausola throws seguita dal nome della classe eccezione scelta; 3. inseri inserire re nel codice codice del me metod todo, o, ove ove l'ecc l'eccezi ezione one si ver verifi ifica, ca, la parola parola chiav chiave e throw,, seguita da un'istanza della classe di eccezione scelta. throw Tra le classi di Java, esiste la classe IllegalArgumentException IllegalArgumentException,, che fa il caso nostro. Un'eccezione di questo tipo indica che è stato passato un argomento illegale ad un metodo. Il codice della classe Impiegato diventa:
Autore: Cinzia Bocchi Ultimo aggiornamento: 10/10/11
7
public class Impiegato { private String nome; private double stipendio; public Impiegato(String Impiegato(String n, double s) { nome= n; setStipendio(s); } public void setStipendio(double setStipendio(double s) throws IllegalArgumentException { if (s>0) (s>0) { stipendio= s; } else { throw new IllegalArgumentException(); } } }
Il codice del programma che usa la classe Impiegato è: import java.util.*; class UsaImpiegato { public static void main(String main(String[]args) []args) { String nome; double stipendio; boolean b= true true;; Scanner console= new Scanner(System Scanner(System.in); .in); System.out.println( System .out.println("Immettere "Immettere il nome"); nome"); nome= console.nextLine(); while(b) while (b) { System.out.println( System .out.println("Immettere "Immettere lo stipendio"); stipendio"); stipendio= console.nextDouble(); try { Impiegato imp= new Impiegato(nome,stipendio); b=false b=false;; } catch(IllegalArgumentException catch (IllegalArgumentException e) { System.out.println( System .out.println("ERRORE: "ERRORE: stipendio negativo"); negativo" ); } } } }
Autore: Cinzia Bocchi Ultimo aggiornamento: 10/10/11
8
Classi di eccezioni Come Come detto detto in preced precedenz enza, a, le eccezi eccezioni oni sono sono istan istanze ze di classi classi deriva derivate te da Throwable. Throwable. La gerarchia si divide in due rami: Error ed Exception. La gerarchia di Error descrive errori interni ed esaurimento delle risorse di sistema. Gli errori di questo tipo non sono recuperabili. L’unica cosa che si può fare è avvisare l’utente e terminare il programma. La gerarchia di Exception si divide in altri due rami: eccezioni che derivano da RunTimeException RunTimeException ed eccezioni che non derivano da essa (per esempio IOException). IOException). Le eccezioni che derivano da RunTimeException sono dovute ad errori di programmazione. programmazione. Tutte T utte le altre sono causate da altri eventi, non dipendenti dal codice, per esempio un errore di I/O.
Throwable
Error
Exception
IOException
RunTimeException
Le eccezioni che derivano da Error o da RunTimeException RunTimeException sono incontrollate, mentre tutte le altre sono controllate e devono essere intercettate obbligatoriamente. obbligatoriamente. Il programmatore può creare classi di eccezioni personalizzate derivandole derivandole da quelle esistenti.
Quest'o Quest'opera pera è stata rilasciata rilasciata con licenza licenza Creative Creative Commons Commons Attributi Attribution-S on-Share hareAlike Alike 3.0 Unported Unported.. Per leggere una copia della licenza visita il sito web http://creativecommons.org/licenses/by-sa/3.0/ o spedisci una lettera a Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
Autore: Cinzia Bocchi Ultimo aggiornamento: 10/10/11
9