edutecnica

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 (e l'identificatone , analogo al parametro formale di un metodo, può essere usato per gestire l'interruzione);

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.