Gestione delle eccezioni in Java
Permettere la costruzione di programmi affidabili
e robusti dovrebbe essere fra gli obiettivi di tutti i linguaggi
di programmazione.
Se durante l'esecuzione di un'istruzione di un programma si verifica una
situazione anomala, il flusso di esecuzione
(normalmente) viene interrotto, ciò avviene quando si verificano eventi
eccezionali (eccezioni).
Sono ad esempio eccezioni:
● La divisione di un numero per zero.
● L'accesso ad un elemento di un vettore
al di fuori dai limiti dello stesso.
● Aprire un file inesistente.
● Scrivere su un disco pieno.
● Mandare in stampa dei dati con la stampante
spenta.
Un software per così dire 'robusto' è in grado di riconoscere da solo l'impossibilità
di proseguire la prevista sequenza di istruzioni segnalando il tipo di anomalia
riscontrato senza andare in crash, perdendo di conseguenza i dati conservati
in memoria.
Nei linguaggi moderni si parla di 'trattamento delle eccezioni' (exception
handling) l'idea di base è quella di prevedere e di intercettare l'eccezione
per poi eseguire un codice 'speciale' prima di rientrare nel flusso normale
di esecuzione del programma.
La cattura di un'eccezione avviene attraverso l'istruzione try/catch.
Try-catch (prova e cattura) serve appunto a 'catturare' l'eccezione:
try{
bloccoRischioso
}catch(tipoDiEccezione id){
bloccoDiTrattamento
}
In sostanza, si cerca di eseguire il bloccoRischioso,
nell'eventualità che si verifichi l'eccezione del tipo tipoDiEccezione
si esegue il bloccoDiTrattamento, accedendo, se necessario
ai metodi del corrispondente oggetto generato, tramite la variabile di riferimento
id.
Esempio: dato un numero inserito da tastiera scrivere in output il suo quadrato.
Soluzione: il problema sarebbe di semplice soluzione se ignorassimo tutte
le possibili eccezioni che potrebbero essere sollevate durante l'input di
tale numero (stringa vuota, numero con lettere, lettere etc..). Senza gestione
delle eccezioni avremmo scritto:
import java.io.*;
class ins {
public static void main (String[] args) throws IOException{
InputStreamReader input=new InputStreamReader(System.in);
BufferedReader s= new BufferedReader(input);
int x=0;
System.out.print("ins.num:");
x=Integer.parseInt(s.readLine().trim());
x=x*x;
System.out.println(x);
}//fine main
}//fine class
Se per sbaglio, invece di inserire un numero inseriamo un carattere, quando
sta cercando di convertire una stringa in un numero il metodo Integer.parseInt(...)
solleva una eccezione:
Exception in thread "main" java.lang.NumberFormatException
java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
at java.lang.Integer.parseInt(Integer.java:458)
at java.lang.Integer.parseInt(Integer.java:499)
at ins.main(ins.java:31)
Usando un blocco try-catch, avremo:
import java.io.*;
class ins {
public static void main (String[] args) throws IOException{
InputStreamReader input=new InputStreamReader(System.in);
BufferedReader s= new BufferedReader(input);
int x=0;
boolean valido=false;
do{
try{
System.out.print("ins.num:");
x=Integer.parseInt(s.readLine().trim());
valido=true;
}catch(NumberFormatException e){
System.out.println("formato
non valido");
}//fine try-catch
}while(!valido);
x=x*x;
System.out.println(x);
}//fine main
}//fine class
In questo caso l'eccezione viene gestita.
Nei listati scritti si riconosce la clausola throws
IOException
..più in generale si potrebbe scrivere:
throws tipoDiEccezione1 [,tipoDiEccezione2,.., tipoDiEccezioneN]
Essa dichiara l'intenzione del programmatore di rinunciare a gestire alcuni
tipi di eccezioni (in questo caso quelle di I/O) si tratta di un contesto
di eccezioni non controllate (unchecked).
Nelle eccezioni controllate, il programmatore è
obbligato ad usare il try-catch; in tal caso il codice precedente verrebbe
scritto come:
import java.io.*;
class ins {
public static void main (String[] args) {
InputStreamReader input=new InputStreamReader(System.in);
BufferedReader s= new BufferedReader(input);
int x=0;
try{
System.out.print("ins.num:");
x=Integer.parseInt(s.readLine().trim());
x=x*x;
System.out.println(x);
}catch(IOException e){
System.out.print("formato non valido");
e.printStackTrace();
}//fine try-catch
}//fine main
}//fine class
Riassumendo, la parola riservata try va seguita
da un blocco di istruzioni, da alcune clausole catch,
il tutto può (opzionalmente) essere terminato da una clausola finally;
la sintassi completa è dunque:
try {
< istruzioni >
} catch ( < tipoDiEccezione1 > < id1 >) {
< istruzioni1 >
} catch ( < tipoDiEccezione2 > < id2 >) {
< istruzioni2 >
} catch ( < tipoDiEccezione3 > < id3 > ) {
< istruzioni3 >
} finally { < istruzioni4 > }
Se una delle istruzioni <istruzioni> lancia una eccezione, l'esecuzione
normale viene sospesa; se l'eccezione lanciata è di tipo <ClasseEccezione1>
(cioè se l'oggetto creato è della classe <ClasseEccezione1>,
vengono eseguite le
se l'eccezione gettata è di tipo <ClasseEccezione2>,
vengono eseguite le <istruzioni2> e così via.
Se vi sono più clausole catch con un parametro di
tipo compatibile con l'eccezione lanciata, solo la prima di esse viene eseguita.
Le <istruzioni4> della clausola finally
vengono comunque sempre eseguite, ogni volta che si è entrati nel blocco
try, immediatamente prima di lasciarlo.