Java - Errori e gestione delle eccezioni

Creato il 02 ottobre 2011 da Numapompilio @Numa_Pompilio

Una delle fasi più importanti dello sviluppo di un codice è senza dubbio la gestione del flusso di errore.
Ogni volta che eseguiamo un'applicazione, risulta estremamente raro che "tutto vada bene" o che ogni cosa si trovi al proprio posto. Ad esempio, se dobbiamo accedere in lettura/scrittura ad un file su disco, può succedere che il suddetto file non esista. Questo genera un errore che, se non gestito a dovere, provoca la chiusura inaspettata del nostro programma.
Ovviamente stiamo parlando di errori a tempo di esecuzione, cioè di errori che si sviluppano una volta che il programma viene avviato.
Java offre un insieme predefinito di eccezioni che possono essere generate durante l'esecuzione di un programma. Per evitare che il programma termini in maniera inaspettata, Java mette a disposizione la possibilità di gestire le eccezioni tramite un costrutto ad hoc.
Essendo Java strutturato secondo il paradigma della programmazione ad oggetti, le eccezioni costituiscono un oggetto, rappresentato quindi da specifiche classi. Le classi che Java ci mette a disposizione sono organizzate secondo una struttura gerarchica.

Struttura gerarchica delle eccezioni Java


La classe Throwable è la superclasse di tutti gli errori (Error) e di tutte le eccezioni (Exception). Gli errori rappresentano eventi che non possono essere controllati dal programmatore (ad esempio l'esaurimento della memoria assegnata al processo in esecuzione), mentre le eccezioni possono essere gestite durante l'esecuzione dell'applicazione.
NullPointException: generata quando il riferimento ad un oggetto usato per invocare un metodo ha in realtà valore null o si tenta di accedere ad una variabile di istanza mediante un riferimento null.
ArrayIndexOutOfBoundException: generata quando si accede ad un elemento di un array usando un indice che è minore di zero o più grande della dimensione dell'array meno uno.
IOException: generata da metodi che accedono a dispositivi di input/output quando si verificano determinate situazioni di errore.
FileNotFoundException: generata nel tentativo di aprire un file non esistente.
NumerFormatException: generata da metodi che effettuano conversioni numeriche.
La clausola throws
I metodi che intendono gestire le eccezioni generate al proprio interno (anche da altri metodi) devono prevedere l'utilizzo della clausola throws. Tale clausola è aggiunta alla segnatura del metodo:
public static void metodo() throws IOException {    ... }
In questo modo la funzione metodo() dichiara di lanciare eccezioni di tipo IOException. Tutte le funzioni che utilizzano metodo() devono rilanciare a loro volta o gestire tale eccezione.
Non è invece richiesto che le sottoclassi di RuntimeException vengano menzionate utilizzando la clausola throws.
Creare nuove eccezioni
Oltre alle eccezioni previste nativamente da Java è possibile crearne di nuove. Ovviamente è necessario creare una nuova classe che estenda Exception oppure una classe sua derivata. Ad esempio:
public class MiaNuovaEccezione extends Exception {    public MiaNuovaEccesione(String msg) {       super(msg);    } }
Abbiamo creato una nuova classe MiaNuovaEccezione che estende Exception. Seguendo le basi dell'ereditarietà, all'interno del costruttore di MiaNuovaEccezione richiamiamo il costruttore della classe Exception mediante l'utilizzo di super(). Passiamo come argomento una stringa che è ciò che vogliamo sia stampato a video una volta che l'eccezione viene lanciata.
L'istruzione throw
Questa istruzione viene utilizzata per lanciare una nuova eccezione.
throw new MiaNuovaEccezione("Ho lanciato MiaNuovaEccezione");
In questo modo lanciamo una nuova eccezione (in questo caso del tipo definito da noi in precedenza) utilizzando throw ed il costruttore della classe MiaNuovaEccezione. Una volta che l'eccezione verrà catturata, questa stamperà a video il messaggio passato al costruttore "Ho lanciato MiaNuovaEccezione".
Esempio:
public class TestMiaNuovaEccezione {    public static void main(String[] args) throws MiaNuovaEccezione {
      int a = 10;       int b = 15;       int c = 5
      if(a<="">          System.out.println("Primo test superato");       }              if(a>c) {          throw new MiaNuovaEccezione("Secondo test ha generato un'eccezione MiaNuovaEccezione");       }
      System.out.println("Fine del programma");    } }
Eseguendo questo banale codice si potrà vedere a video: $ Primo test superato $ Secondo test ha generato un'eccezione MiaNuovaEccezione
L'istruzione di stampa "Fine del programma" non verrà mai eseguita poichè il programma verrà dismesso dopo il lancio dell'eccezione.

Gestire una eccezione
Quando viene lanciata un'eccezione, questa genera una catena di terminazione di metodi che inizia con il metodo che esegue throw e risale fino al main. L'eccezione deve essere gestita (catturata) da uno di tutti questi metodi.
Per catturare un'eccezione di utilizza il costrutto try-catch-finally.
try {    blocco-try } catch(ClasseEccezione1 e) {    blocco-catch } catch(ClasseEccezione2 e) {    blocco-catch } ... finally {    blocco-finally }
Il blocco-try racchiude tutte quelle funzioni che generano eccezioni che vogliamo catturare e gestire.
Il blocco-catch racchiude tutte le istruzioni che vogliamo eseguire quando nel blocco-try viene catturata un'eccezione del tipo ClasseEccezione1 (ecc.)
Il blocco-finally racchiude tutte le istruzioni che vogliamo eseguire in ogni caso. Si può anche omettere questo blocco se non necessario.