Eccezioni controllate e non controllate

Le eccezioni si dividono in due categorie:
  • Eccezioni controllate (checked)

  • Eccezioni non controllate (unchecked)
Le eccezioni controllate DEVONO essere gestite esplicitamente dal programma, altrimenti il compilatore segnalerà un errore.

Ogni volta che scriviamo un'istruzione che potrebbe lanciare un'eccezione controllata, allora

  • l'istruzione deve essere racchiusa in un blocco try-catch che possa gestire quel tipo di eccezione;
oppure
  • il metodo che contiene l'istruzione deve delegare la gestione dell'eccezione al chiamante, con la clausola throws che adesso vediamo.

  •  



Sintassi della clausola throws


 
<modificatori> <tipo> <nome-metodo>  (<lista-parametri>
               throws  <Classe-ecc1>, ..., <Classe-eccN>
 
   <corpo-metodo>

}

La clausola throws viene inserita nella dichiarazione del metodo per informare il compilatore che durante l'esecuzione di quel metodo possono essere generate eccezioni (controllate) dei tipi elencati dopo la parola chiave throws, la cui gestione viene delegata al chiamante.

Cattivo esempio: Per evitare di dover gestire le eccezioni potremmo scrivere tutti i metodi così:
 


    public void metodo() throws Exception
        <istruzioni>;
    }

O magari fingere di gestire ogni genere di eccezione in modo che il compilatore non segnali errori:
 


    public void metodo() {
       try {
          <istruzioni>;
       }
       catch (Exception e) { } // catturata ma non gestita!
    }

Così si perde molta informazione sul programma. Ignorare un problema non porta mai alla sua soluzione!
 




Quali eccezioni sono controllate?

  • Sono non controllate tutte le eccezioni che estendono Error e RuntimeException

  • Sono controllate (dal compilatore) tutte le altre eccezioni.
Object
  |
  +----Throwable
          |
          +----Error
          |      |
          |      +----VirtualMachineError
          |      ...
          |
          +----Exception
                  |
                  +----RuntimeException
                  |         |
                  |         +----ArithmeticException
                  |         |
                  |         +----IndexOutOfBoundsException
                  |         |
                  |         +----ClassCastException
                  |         ...
                  |
                  +----IOException
                  |         |
                  |         +----EOFException
                  |         |
                  |         +----FileNotFoundException
                  |         ...
                  |
                  +----ClassNotFoundException
                  ...

Perché?

  • Una istanza di Error non è prevedibile, e comunque non può essere gestita da programma.

  •  
  • Una eccezione di tipo RunTimeException può verificarsi ovunque nel programma:  un controllo esplicito appesantisce i programmi senza aumentare l'informazione.
    Corrispondono spesso a errori logici del programma (codice "non robusto" per mancanza di controlli).

  •  
  • Il controllo esplicito richiesto per gli altri tipi di eccezioni consente di localizzare rapidamente le parti del programma che potrebbero lanciarle.


Riassumendo:

  • Il compilatore verifica che le eccezioni controllate vengano gestite.

  •  
  • Le eccezioni controllate sono dovute a circostanze esterne che anche il più attento dei programmatori non può escludere semplicemente scrivendo codice robusto.

  •  
  • Le eccezioni non controllate sono dovute a errori fatali che non ha senso prevedere nel codice (es. OutOfMemoryException, StackOverflowError) oppure ad errori logici (es. ClassCastException, IllegalArgumentException, NumberFormatException, IndexOutOfBoundsException, NullPointerException) che il programmatore dovrebbe gestire con controlli opportuni, senza dover ricorrere al meccanismo delle eccezioni.



Lanciare un'eccezione: throw

In tutti gli esempi visti l'eccezione era lanciata dall'interprete quando si verificava una situazione imprevista.

Talvolta un metodo può trovarsi a non poter gestire dei dati che riconosce come errati (ad esempio, ricevendo dei dati in lettura che non corrispondono al formato atteso)... Cosa fare in questi casi?

Invece di elaborare dati errati si può lanciare un'eccezione con il comando throw.

Sintassi
 


// <eccezione> è un oggetto (di una sottoclasse) della classe Exception

throw <eccezione>;
 


// di solito si crea un opportuno oggetto

throw new <Sottoclasse-Exception>(<messaggio-errore>);
 

Importante:

  • Il comando si chiama throw, simile ma diverso dalla clausola throws!!!

  •  
  • Si può lanciare un'eccezione di qualunque tipo, sia controllata che non. Se è controllata, deve rispettare le regole viste.
Esempio: lanciamo un'eccezione non controllata (Sqrt)
 
public class Sqrt { 

    // calcola la radice quadrata di x, se e' positivo 
    public static double sqrt(double x) {

        // lancia un'eccezione se il parametro attuale e' illegale 
        if (x < 0)
            throw new IllegalArgumentException("sqrt: " + x);

        return Math.sqrt(x);
    }


    public static void main(String [] args) {  
        double x; 
        do { 
            System.out.print("Scrivi un intero (0 per finire): ");
            x = Input.readInt(); 
            if (x != 0) {
                System.out.println("Risultato di sqrt("+x+"):"); 
                // System.out.println(Math.sqrt(x)); 
                System.out.println(sqrt(x)); 
            } 
        } while(x != 0); 
    } 
}



Definire le proprie eccezioni

  • Java fornisce una ricca gerarchia di eccezioni. E' buona norma usare le eccezioni predefinite, cercando nella API di Exception [locale, Medialab, Sun] se esiste una eccezione adeguata.

  • Comunque,  un utente può definire una sua classe di eccezioni e usarle come quelle di Java: può lanciarle, catturarle, delegarne la gestione.

  • Le eccezioni di una nuova classe sono controllate oppure non controllate a seconda della superclasse.

Un esempio: IllegalTimeException

Supponiamo di avere definito la classe Time contenente il costruttore di default, il metodo toString(), ed il metodo setTime() per impostare un orario ammissibile. Come succede spesso nella progettazione di classi di questo tipo, dobbiamo decidere come gestire il passaggio di valori errati al metodo setTime().

Una soluzione semplice è la seguente:

public class Time {
    private int ore, minuti, secondi; 
    
    public Time () {   // costruttore
        ore = minuti = secondi = 0;
    }

    public String toString () {
        return (ore + ":" + minuti + ":" + secondi);
    }

    public boolean setTime (int oo, int mm, int ss) {
        if ( 0 <= oo && oo < 24 &&  // oo in [0,23]
             0 <= mm && mm < 60 &&  // mm in [0,59]
             0 <= ss && ss < 60 ) { // ss in [0,59]
            ore = oo;
            minuti = mm;
            secondi = ss;
            // se i parametri sono ok restituisce true
            return true;
        } else
            // con parametri errati restituisce false
            return false;
    }
}

Una alternantiva ragionevole in questa situazione è quella di lanciare un'opportuna eccezione se il valore dei parametri è errato, ad esempio una IllegalArgumentException: dopotutto è dovere di chi invoca il metodo passare valori corretti.

Per indicare in maniera più precisa la natura dell'errore possiamo definire una nuova classe di eccezioni IllegalTimeException:

public class IllegalTimeException extends RuntimeException { 

    public IllegalTimeException() {
        super(); 
    }

    public IllegalTimeException(String msg) { 
        super(msg); 
    }
}

Adesso possiamo estendere la classe Time con la classe RobustTime, sovrascrivendo opportunamente il metodo setTime().

public class RobustTime extends Time {
    
    public RobustTime () {   // costruttore
        super();
    }

    public boolean setTime (int oo, int mm, int ss) {
        if ( 0 <= oo && oo < 24 &&  // oo in [0,23]
             0 <= mm && mm < 60 &&  // mm in [0,59]
             0 <= ss && ss < 60 )   // ss in [0,59]
            super.setTime(oo,mm,ss);
        else
            // con parametri errati lancia eccezione
            throw new IllegalTimeException("Argomenti di setTime errati");
        return true;
    }
}

Infine possiamo testare la classe col seguente programma TestTime :

public class TestTime {

    public static void main(String[] args) {

        System.out.print("    Ore [0,23] => ");
        int ore = Input.readInt();
        System.out.print(" Minuti [0,59] => ");
        int min = Input.readInt();
        System.out.print("Secondi [0,59] => ");
        int sec = Input.readInt();

        // Time t = new Time (); 
        Time t = new RobustTime(); 

        try {
            t.setTime(ore,min,sec);
            System.out.print("Sono le ore " + t); 
        } catch (IllegalTimeException e) { 
            System.out.println ("Errore:" + e); 
        }
    }
}