Skip to content

19. Eccezioni e Gestione degli Errori

Indice


Le Exceptions sono il meccanismo strutturato di Java per gestire condizioni anomale a runtime.

Permettono ai programmi di separare il flusso di esecuzione normale dalla logica di gestione degli errori, migliorando robustezza, leggibilità e correttezza.

19.1 Gerarchia e tipi di eccezioni

Tutte le eccezioni derivano da Throwable.

La gerarchia definisce quali condizioni sono recuperabili, quali devono essere dichiarate e quali rappresentano errori fatali del sistema.

java.lang.Object
└── java.lang.Throwable
    ├── java.lang.Error
    └── java.lang.Exception
        └── java.lang.RuntimeException

19.1.1 Throwable

  • Classe base per tutti gli errori e le eccezioni
  • Supporta messaggio, causa e stack trace
  • Solo Throwable (e le sue sottoclassi) può essere lanciato o catturato

19.1.2 Error (unchecked)

  • Rappresenta gravi problemi della JVM o del sistema
  • Non è pensato per essere catturato o gestito
  • Esempi: OutOfMemoryError, StackOverflowError

Note

  • Gli Error indicano condizioni dalle quali l’applicazione generalmente non è attesa a riprendersi.

19.1.3 Eccezioni Checked (Exception)

  • Sottoclassi di Exception escludendo RuntimeException
  • Rappresentano condizioni che le applicazioni potrebbero voler gestire
  • Devono essere catturate oppure dichiarate
  • Esempi: IOException, SQLException

19.1.4 Eccezioni Unchecked (RuntimeException)

  • Sottoclassi di RuntimeException
  • Non è richiesto dichiararle o catturarle
  • Solitamente rappresentano errori di programmazione
  • Esempi: NullPointerException, IllegalArgumentException

19.2 Dichiarare e lanciare eccezioni

19.2.1 Dichiarare eccezioni con throws

Un metodo dichiara eccezioni checked usando la clausola throws. Questa fa parte del contratto API del metodo.

void readFile(Path p) throws IOException {
    Files.readString(p);
}

Note

  • Solo le eccezioni checked devono essere dichiarate.
  • Le eccezioni unchecked possono essere dichiarate, ma di solito vengono omesse.

19.2.2 Lanciare eccezioni

Le eccezioni vengono create con new e lanciate esplicitamente usando throw.

if (value < 0) {
    throw new IllegalArgumentException("value must be >= 0");
}
  • throw lancia esattamente un’istanza di eccezione
  • throws dichiara le possibili eccezioni nella firma del metodo

19.3 Override dei metodi e regole sulle eccezioni

Quando si fa override di un metodo, le regole sulle eccezioni sono applicate in modo rigoroso.

  • Un metodo in override può lanciare meno o più specifiche eccezioni checked
  • Può lanciare qualsiasi eccezione unchecked
  • Non può lanciare nuove o più generiche eccezioni checked
class Parent {
    void work() throws IOException {}
}

class Child extends Parent {
    @Override
    void work() throws FileNotFoundException {} // OK (sottoclasse)
}

Note

  • Cambiare solo le eccezioni unchecked non rompe mai il contratto di override.

Important

Ricorda: i costruttori seguono una regola diversa.

Un Costruttore deve dichiarare tutte le eccezioni checked dichiarate nel costruttore di base (o le superclassi di quelle eccezioni checked).

Può anche dichiarare eccezioni checked aggiuntive. Questo comportamento è l’opposto di quello dell’overriding dei metodi.

Un metodo che override non può lanciare alcuna eccezione checked diversa da quelle dichiarate dal metodo overridato. Può lanciare solo sottoclassi di tali eccezioni.


19.4 Gestione delle eccezioni: try, catch, finally

19.4.1 Sintassi base try-catch

try {
    riskyOperation();
} catch (IOException e) {
    handle(e);
}
  • Un blocco try deve essere seguito da almeno un catch oppure da un finally
  • I catch vengono controllati dall’alto verso il basso

19.4.2 Blocchi catch multipli

Blocchi catch multipli permettono gestioni diverse per tipi di eccezione differenti.

try {
    process();
} catch (FileNotFoundException e) {
    recover();
} catch (IOException e) {
    log();
}

Note

  • Le eccezioni più specifiche devono venire prima di quelle più generali, altrimenti la compilazione fallisce.
  • Se si mette un catch per una superclasse (es. IOException) prima di uno per una sottoclasse (es. FileNotFoundException), il catch della sottoclasse diventa irraggiungibile.

19.4.3 Multi-catch (Java 7+)

try {
    process();
} catch (IOException | SQLException e) {
    log(e);
}
  • I tipi di eccezione devono essere non correlati (nessuna relazione parent/child)
  • La variabile catturata è implicitamente final

19.4.4 Blocco finally

Il blocco finally viene eseguito indipendentemente dal fatto che venga lanciata un’eccezione, tranne in casi estremi di terminazione della JVM.

try {
    open();
} finally {
    close();
}
  • Usato per logica di cleanup
  • Viene eseguito anche se si usa return nel blocco try e/o catch

Note

  • Un blocco finally può sovrascrivere un valore di ritorno o “inghiottire” un’eccezione. Questo è generalmente sconsigliato perché rende il flusso di controllo più difficile da comprendere.

Important

  • Quando sia un blocco catch sia un blocco finally lanciano un’eccezione, l’eccezione lanciata nel blocco finally è quella che viene propagata dal metodo.

  • L’eccezione lanciata nel blocco catch viene persa e non viene aggiunta alla lista delle eccezioni soppresse.

try {
    throw new RuntimeException("try");
} catch (RuntimeException e) {
    throw new RuntimeException("catch");
} finally {
    throw new RuntimeException("finally");
}

In questo caso, viene lanciata solo l’eccezione "finally".


19.5 Gestione automatica delle risorse (try-with-resources)

Il try-with-resources fornisce la chiusura automatica delle risorse che implementano AutoCloseable.

Elimina la necessità di cleanup esplicito con finally nella maggior parte dei casi.

19.5.1 Sintassi base

try (BufferedReader br = Files.newBufferedReader(path)) {
    return br.readLine();
}
  • Le risorse vengono chiuse automaticamente
  • La chiusura avviene anche se viene lanciata un’eccezione
  • Le risorse vengono chiuse prima dell'esecuzione dei blocchi catch o del finally
try (Resource a = new Resource()) {
    a.read();
} finally {
    a.close();  // ❌ Compile-time error: a è out of scope qui
}

19.5.2 Dichiarare risorse multiple

try (InputStream in = Files.newInputStream(p);
        OutputStream out = Files.newOutputStream(q)) {
    in.transferTo(out);
}
  • Le risorse vengono chiuse in ordine inverso rispetto alla dichiarazione

19.5.3 Scope delle risorse

  • Le risorse sono visibili solo all’interno del blocco try
  • Sono implicitamente final
  • Da Java 9, si possono dichiarare risorse in anticipo, fuori dal try-with-resources, purché siano dichiarate final o siano effettivamente final.
final var firstWriter = Files.newBufferedWriter(filePath);

try (firstWriter; var secondWriter = Files.newBufferedWriter(filePath)) {
    // CODE
}

Note

  • Tentare di riassegnare una variabile risorsa causa un errore di compilazione.
Resource a = new Resource();
try(a){ // since Java 9
  ...
}finally{
   a.close(); // questo codice compila ma la risorsa puntata dal reference 'a', è stata chiusa.
}

19.6 Eccezioni soppresse

Quando sia il blocco try sia il metodo close() della risorsa lanciano eccezioni, Java conserva l’eccezione principale e sopprime le altre.

try (BadResource r = new BadResource()) {
    throw new RuntimeException("main");
}

Se anche close() lancia un’eccezione, questa diventa soppressa.

catch (Exception e) {
    for (Throwable t : e.getSuppressed()) {
        System.out.println(t);
    }
}
  • L’eccezione principale viene lanciata
  • Le eccezioni secondarie sono accessibili tramite getSuppressed()

Important

  • Le eccezioni soppresse vengono generate solo dal blocco finally implicito creato dal try-with-resources.

  • Al contrario, le eccezioni lanciate in un blocco finally esplicito non vengono soppresse: sostituiscono qualsiasi eccezione precedente e diventano l’unica eccezione propagata.


19.7 Riepilogo sulle eccezioni

  • Le eccezioni checked devono essere catturate o dichiarate
  • I metodi in override non possono ampliare le eccezioni checked
  • Usa il multi-catch per logica di gestione condivisa
  • Preferisci try-with-resources al cleanup con finally
  • Le risorse si chiudono in ordine inverso
  • Le eccezioni soppresse preservano il contesto completo del fallimento