Eccoci ad una parte molto importante della programmazione in Java: le eccezioni.

Le eccezioni sono dei meccanismi che ci permettono di gestire delle situazioni non previste all’interno del nostro programma, che altrimenti sarebbero state gestite in qualche modo dalla JVM (o in generale, dal sistema).

Un esempio famoso di eccezione, è la BOSD di Windows che pochi -spero- hanno avuto la sfortuna di incontrare:

BSOD wallpaper :)
BSOD wallpaper 🙂

In ambito di programmazione Java invece, uno dei casi più comuni è una eccezione di tipo ArrayIndexOutOfBoundsException, che ci appare in caso volessimo accedere all’ elemento n+1 di un array lungo n.

Le eccezioni vengono visualizzate sulla JVM e interrompono il programma, a differenza di altri linguaggi (come il C) la macchina virtuale ci fornisce qualche indicazione utile per capire dove si trova il problema:

public class Prova
{
    public static void main (String[] args)
    {
         int[] array = {0,1,2,3,4};
         System.out.println(array[5]);
    }
}
//Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5

Nell’errore, che trovate nel commento all’ ultima riga, la JVM ci suggerisce la posizione del problema: nel metodo “main”, nome dell’ eccezione (che solitamente ci aiuta a capire il problema) : numero della riga.

Un’ idea, che avranno chi conosce il C, potrebbe essere quella di gestire gli errori sul momento e restituire un numero per errore (come spesso si fa con -1 quando una cosa non è presente).

Questo fà sorgere molti problemi di codifica, ci riduce la possibilità di valori di return e ci rende più difficile modificare in futuro il codice del nostro programma.

Le eccezioni oltre tutto, vengono anche propagate verso l’alto nelle chiamate dello stack in modo da avere sempre sotto controllo la situazione.

Inoltre, grazie al polimorfismo, una volta gestita un tipo di errore una volta poi ne siamo sempre al sicuro.

Con le eccezioni, possiamo gestire solo gli Errori Sincroni: ovvero quelli che si verificano all’esecuzione di un’istruzione, sia di tipo Critico (errori interni alla JVM) sia di tipo non critico (errori derivati da condizioni anomale); mentre non possiamo gestire gli Errori Asincroni (eventi che accadono durante l’esecuzione del programma e quindi non controllabili direttamente).

Catturare l’errore

Per catturare l’errore, utilizziamo i blocchi try…catch…finally

5718_389674864462808_1991750263_n

Prima viene eseguito il blocco try: se viene trovata un’eccezione, si viene rimandati al blocco catch e una volta terminato il blocco di istruzioni si comincia con la riga successiva al blocco di istruzioni contenuti (ed eseguiti) in finally. Vediamo meglio nel dettaglio come funzionano:

Try

Il blocco try va inserito per primo: qui viene contenuto il blocco di istruzioni dalle quali può essere lanciata l’eccezione:

public void entrareInMetro()
{
    try
    {
       metro.entra();
    }
}

Catch

Nel blocco catch inseriamo come parametro il tipo di eccezione che vogliamo intercettare, e nel blocco le istruzioni da eseguire in caso viene intercettata.

catch ( AbbonamentoNonValidoException e)
{
    //Fai qualcosa con l'eccezione e
}

Possiamo anche definire più blocchi di catch per gestire più eccezioni:

public void entrareInMetro()
{
    try
    {
        metro.entra();
    }
    catch ( AbbonamentoNonValidoException e1)
    {
        //Fai qualcosa con l'eccezione e1
    }
    catch ( BigliettoNonTimbratoException e2)
    {
        //Fai qualcosa con l'eccezione e2
    }
}

Anche l’ ordine in cui inseriamo i blocchi catch è importante: determina infatti in base al tipo, quale blocco verrà eseguito per primo.

Ovvero:

public void entrareInMetro()
{
    try
    { 
       metro.entra(); 
    } 
    catch ( MultaException e2) 
    { 
        //Fai qualcosa con l'eccezione e2
    }
    catch ( AbbonamentoNonValidoException e1)
    {
        //Fai qualcosa con l'eccezione e1
    }
    catch ( BigliettoNonTimbratoException e2)
    {
        //Fai qualcosa con l'eccezione e2
    }
}

Questo ovviamente nel caso in cui BigliettoNonTimbratoException e AbbonamentoNonValidoException siano sottoclassi di MultaException

In questo caso, sia quando abbiamo AbbonamentoNonValidoException, sia quando abbiamo AbbonamentoNonValidoException, non verranno eseguiti i rispettivi blocchi catch, ma verrà eseguito direttamente MultaException (in questo caso Eclipse vi darà un errore dicendo che infatti questi due blocchi non verranno mai eseguiti).

Quindi, prima dobbiamo cercare di utilizzare eccezioni più specifiche, poi quelle più generali: per fare ciò ci basta semplicemente collocarle prima del blocco che comprende una più vasta gamma di errori.

Inoltre, all’ interno dei blocchi catch è possibile lanciare nuove eccezioni:

public void entrareInMetro() throws DatiRichiestiException
{
    try
    {
        metro.entra();
    }
    catch (MultaException e2)
    {
       throw new DatiRichiestiException();
    }
}

Finally

Il blocco finally viene eseguito qualunque sia l’esito delle istruzioni in try, ovvero anche se viene lanciata l’eccezione le istruzioni contenute in questo blocco verranno eseguite. Inoltre, l’inclusione di questo blocco non è obbligatoria: se non ne abbiamo bisogno, possiamo farne a meno.

Politica Catch-Or-Declare

Una volta lanciata l’eccezione, abbiamo due possibilità:

  • Catch: catturare l’eccezione e gestirla in qualche modo;
  • Declare: dichiariamo affianco al metodo l’elenco di eccezioni sollevabili, e affidiamo al chiamante di catturare l’eccezione.

Nel caso delle eccezioni, questa politica è obbligatoria: dovremo sempre o catturarla o dichiararla.

Il catch, l’abbiamo visto con i tre blocchi poco fà. Per il declare invece, dobbiamo dichiarare la lista di eccezioni con la parola chiave throws:

public void entraInMetro() throws BigliettoNonTimbratoException, AbbonamentoNonValidoException
{
    mentro.entra();
}

Definire nuove eccezioni

E’ molto importante definire eccezioni appropriate, in base ai vari errori che riceviamo.

Al programmatore poi, è ancora più utile cercare di dare definizioni di eccezioni il più specifiche possibile, in modo da riuscire a capire dove si trova il problema.

Quando troviamo un’ eccezione, la JVM ci riporta:

  • Il nome del thread che ha lanciato l’errore
  • Il nome dell’ eccezione
  • Lo call stack in ordine di chiamate dei metodi
  • Il file e la riga colpevoli.

Diciamo quindi che queste informazioni, unite ad una corretta eccezione, possono essere veramente d’aiuto nel cercare di capire il problema incontrato dal nostro programma.

Vediamo infine, come definire una nuova eccezione:

class BigliettoNonTimbratoException extends Exception { }

Per farlo, ci basta semplicemente estendere la superclasse Exception, che a sua volta estende la classe Throwable.