Eccezioni controllate e non controllate

Le eccezioni si dividono in due classi:
  • Eccezioni controllate (checked)
  • Eccezioni non controllate (unchecked)
Le eccezioni controllate DEVONO essere gestite esplicitamente dal programma. Se un'istruzione può lanciare un'eccezione controllata, allora
  • l'istruzione deve essere  in un blocco try-catch che gestisce quel tipo di eccezione; 
oppure
  • il metodo che contiene l'istruzione deve delegare la gestione al chiamante, con la clausola throws.

  •  
Vediamo un esempio che usa l'eccezione controllata FileNotFoundException, sottoclasse di IOException.



Esempio: gestione delegata al chiamante

main invoca metodo1 che invoca metodo2. L'eccezione si può verificare in metodo1 che ne delega la gestione al chiamante (con la clausola "throws IOException"), e così fanno anche metodo2 e main. Se si toglie questa clausola, si verifica un errore di compilazione.
 
import java.io.*;

// Scaricare il file "input.txt"; compilare ed eseguire la classe;
// quindi cancellare "input.txt" e rieseguire la classe.

public class NestedFileNotFound {

    public static void metodo2() throws IOException
        System.out.println("    Invocato metodo 2");
    FileReader reader = new FileReader("input.txt");
    BufferedReader in = new BufferedReader(reader);
    String line = in.readLine();
        System.out.println(line);
        System.out.println("    Terminato metodo 2");
    }

// Provare a togliere la clausola throws
//     public static void metodo1() {

    public static void metodo1() throws IOException
        System.out.println("  Invocato metodo 1");
        metodo2();
        System.out.println("  Terminato metodo 1");
    }

    public static void main(String [] args) throws IOException
        System.out.println("Invocato main");
        metodo1();
        System.out.println("Terminato main");
    }
}




Esempio: gestione con try-catch

Poiché ora metodo1 racchiude la chiamata di metodo2 in un blocco try-catch, non ha più bisogno della clausola throws
 
import java.io.*;

// Scaricare il file "input.txt"; compilare ed eseguire la classe;
// quindi cancellare "input.txt" e rieseguire la classe.

public class CatchFileNotFound {

    public static void metodo2() throws IOException {
        System.out.println("    Invocato metodo 2");
        FileReader reader = new FileReader("input.txt");
        BufferedReader in = new BufferedReader(reader);
        String line = in.readLine();
        System.out.println(line);
        System.out.println("    Terminato metodo 2");
    }

    public static void metodo1() { 
        System.out.println("  Invocato metodo 1");
    try {
            metodo2();
        } catch (IOException ecc){
            System.out.println("C'e' stato un problema in metodo2...");
        }
        System.out.println("  Terminato metodo 1");
    }

    public static void main(String [] args) { 
        System.out.println("Invocato main");
        metodo1();
        System.out.println("Terminato main");
    }
}




Sintassi della clausola throws


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

}

Informa il compilatore che durante l'esecuzione del metodo <nome-met> possono essere generate eccezioni dei tipi elencati dopo throws, la cui gestione viene delegata al chiamante.

Esempio
 


    public static void metodo2() throws IOException
        System.out.println("    Invocato metodo 2");
        FileReaderreader = newFileReader("input.txt");
       BufferedReader in = new BufferedReader(reader);
        Stringline = in.readLine();
        System.out.println(line);
        System.out.println("    Terminato metodo 2");
    }

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


    public static void metodo() throws Exception
        <Istruzioni>;
    }

Si perde molta informazione sul programma.
 




Quali eccezioni sono controllate?

  • Sono non controllate tutte le eccezioni che derivano da Error e RuntimeException
  • Sono controllate tutte le altre eccezioni.
                        Object
                           |
                        Throwable
                       /          \
                      /            \
             Exception            Error
             /       \                 \
    IOException  RuntimeException     VirtualMachineError
          |                    \
    EOFException          ArithmeticException
    FileNotFounException  ArrayIndexOutOfBoundsException
                           ClassCastException

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.



Lanciare un'eccezione: throw

In tutti gli esempi visti l'eccezione era lanciata dall'interprete quando si verificava una situazione imprevista. Ma si può lanciare un'eccezione anche da programma con throw.

Sintassi
 

throw <eccezione>;

// dove <eccezione> e' un oggetto di (sottoclasse) di Exception



// di solito, 

throw new <ExceptionClass>(<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
 
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) { 
      ConsoleReader console = new ConsoleReader(System.in);
      double x;
      do{
            System.out.print("Scrivi un intero (0 per finire): ");
            x = console.readInt();
          if (x != 0){
                System.out.println("Risultato di 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.
Esempio

Definiamo la classe di eccezioni TempoException:
 

public class TempoException extends RuntimeException{

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

Estendiamo Tempo.java in modo che se il metodo assegnaTempo riceve parametri errati, genera un'eccezione. Si noti l'uso estensivo di super per riutilizzare al massimo la superclasse Tempo.
 

public class TempoConEccezioni extends Tempo{
    public TempoConEccezioni() { 
        super();
    }
    public TempoConEccezioni (int ora, int minuto, int secondo) {
         super (ora, minuto, secondo);
    }
    public int assegnaTempo (int ora, 
                           int minuto, int secondo){
      if (super.assegnaTempo(ora,minuto,secondo) == 0)
            return 0;
      else throw new TempoException("Errore in assegnatempo");
    }
}

Proviamo la nuova classe con un semplice programma:
 

public class UsaTempoConEccezioni{

    public static void main(String [] args) { 
      try  {
         Tempo t = new TempoConEccezioni (99,99,99);
           t.visualizza(true);
        } catch (TempoException e) {
              System.out.println ("Errore:" + e);
        }
   }
}