Gestione delle eccezioni in Java

  • Meccanismo che permette di trattare queste situazioni in modo flessibile ed elegante

  •  
  • Perfettamente integrato con la metodologia orientata ad oggetti
Come funziona?
  • Quando si verifica un imprevisto, il metodo attivo getta o lancia (throws) un'eccezione che viene passata al metodo chiamante. Il metodo attivo termina l'esecuzione (come con return).

  •  
  • Per default, un metodo che riceve un'eccezione termina l'esecuzione e passa l'eccezione al metodo chiamante.

  •  
  • Quando l'eccezione raggiunge main, l'esecuzione del programma termina stampando un opportuno messaggio di errore.
Ma un'eccezione lanciata da un metodo può essere catturata (catch) dal metodo chiamante, e trattata con opportune istruzioni.

Ad esempio, se in un ciclo che legge dati da internet cade la connessione, è naturale gestire questa situazione da programma senza causarne necessariamente la terminazione.




Esempio di gestione default

Si compili e si esegua la seguente classe:
 
1    public class NestedNullPointer {
2    public static void bar(){
3          Object o = null;
4          System.out.println(o.toString());
5       }
6    public static void foo(){
7      bar();
8       }
9    public static void main (String [] args){
10         foo();
11      }
12   }

La macchina astratta Java scriverà qualcosa come:
 

> java NestedNullPointer
java.lang.NullPointerException
        at NestedNullPointer.bar(NestedNullPointer.java:4)
        at NestedNullPointer.foo(NestedNullPointer.java:7)
        at NestedNullPointer.main(NestedNullPointer.java:10)

elencando la catena dei metodi attivi nel momento in cui si verifica l'eccezione (bar - foo - main) e per ogni metodo la linea di codice dove si  è verificata.




Gerarchie di eccezioni

Per comprendere bene il meccanismo di gestione delle eccezioni, è utile vedere come questo si integra con il paradigma orientato ad oggetti: 
  • Le eccezioni in Java sono oggetti, istanze di (sottoclassi di) Throwable ("lanciabile'');

  •  
  • L'ereditarietà consente di definire gerarchie di eccezioni: utili per distinguere tra le varie situazioni anomale, e gestire ognuna nel modo opportuno;

  •  
  • Ricca gerarchia di eccezioni predefinite;

  •  
  • Si possono definire nuove eccezioni per meglio caratterizzare le situazioni che si possono verificare in specifiche applicazioni.



Un pezzo della gerarchia

Un pezzetto della gerarchia di classi di eccezioni di Java:


                         Object
                           |
                        Throwable
                       /          \
                      /            \
              Exception             Error 
             /       \                 \
     IOException   RuntimeException     VirtualMachineError
          |                    \
    EOFException           ArithmeticException
    FileNotFounException   ArrayIndexOutOfBoundsException
                           ClassCastException

 
  • Tutte le classi di eccezioni sono sottoclassi di Throwable

  •  
  • Le sottoclassi di Error rappresentano errori fatali software (della Java Virtual Machine) o hardware (crash di un disco)
 



Catturare un'eccezione: try e catch

Se una chiamata di metodo può generare un'eccezione, possiamo racchiuderla in un blocco try, seguito da uno o più blocchi catch contenenti le istruzioni da eseguire se l'eccezione viene lanciata.

Esempio: Stampa di un array:
 

public class CatchOutOfBounds {
    public static void main(String [] args){
    int [] array = new int [5];
    for (int i = 0; i < array.length; i++){
             array[i] = (int) (100 * Math.random());
        }
        System.out.println("Contenuto dell'array:");
    try{
    int i = 0;
    while (true)
             System.out.println(array[i++]);
        }
    catch(ArrayIndexOutOfBoundsException e){
        System.out.println("Stampa terminata...");
        }
    }
}

Attenzione: è un buon esempio di try-catch, ma è un pessimo stile di programmazione!!!




Sintassi di try-catch-finally

 
try
   <istruzioni-try>; 
}
catch (<classe-eccezione1><id1>){
   <istruzioni-catch1>;
}
...
catch (<classe-eccezioneN><idN>){
   <istruzioni-catchN>;
}
finally {
   <istruzioni-finally>;
}
  • Se c'è almeno un blocco catch, allora il blocco finally è facoltativo.

  • "<istruzioni-XXX>;" sono sequenze di istruzioni Java.



Significato di try-catch-finally

  • Si eseguono le <istruzioni-try>.

  •  
  • Se l'esecuzione termina senza fallimenti, si eseguono le eventuali <istruzioni-finally> e si termina.

  •  
  • Altrimenti, se una istruzione in <istruzioni-try> lancia un'eccezione ecc, si cerca il primo blocco catch tale che ecc sia istanza di <classe-eccezioneX>.

  •  
  • Se un tale blocco esiste, si eseguono le <istruzioni-catchX>  dopo aver associato ecc all'identificatore <idX>; infine si eseguono le eventuali <istruzioni-finally>. L'eccezione è stata catturata con successo.

  •  
  • Se invece ecc non è istanza di nessuna <classe-eccezioneX>, allora vengono comunque eseguite le eventuali <istruzioni-finally>, e l'eccezione viene passata al chiamante.
Note:
  • Le eventuali <istruzioni-finally> vengono eseguite sempre, anche se, ad esempio, c'è un return del blocco trycatch. Il blocco finally può contenere delle istruzioni che chiudono dei files oppure rilasciano delle risorse, per garantire la consistenza dello stato.
     
  • I costrutti try-catch-finally possono essere annidati a piacere.  Esempio: se in un blocco catch o finally può essere generata un'eccezione, si possono racchiudere le sue istruzioni in un altro blocco try.