W3docs

Gerarchia delle eccezioni in Java

Come Throwable, Error, Exception e RuntimeException si relazionano nella gerarchia delle eccezioni di Java.

Ogni eccezione in Java fa parte di un piccolo albero di classi con radice in java.lang.Throwable. Conoscere la forma di quell'albero ripaga costantemente: spiega perché catch (Exception e) non cattura OutOfMemoryError, perché RuntimeException è speciale e perché alcune eccezioni ti obbligano a gestirle mentre altre no. L'intera struttura entra in un solo diagramma.

Questa pagina mappa quell'albero: la radice Throwable, i rami Error ed Exception, dove cade la linea tra checked e unchecked, e come tutto ciò determina cosa catturano effettivamente i tuoi blocchi catch.

L'albero completo

Throwable
├── Error                       (unchecked — JVM-level)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   ├── VirtualMachineError
│   └── ...
└── Exception                   (checked by default)
    ├── IOException             (checked)
    ├── SQLException            (checked)
    ├── ClassNotFoundException  (checked)
    ├── ...
    └── RuntimeException        (unchecked)
        ├── NullPointerException
        ├── IllegalArgumentException
        ├── IndexOutOfBoundsException
        ├── ArithmeticException
        ├── ClassCastException
        ├── IllegalStateException
        └── ...

L'intero albero è una sola gerarchia di classi. Ecco perché un catch per un supertipo cattura i suoi sottotipi, perché le variabili di eccezione si comportano come riferimenti ordinari e perché puoi tenere un IOException in un campo di tipo Exception.

Throwable — la radice

Throwable è ciò che throw accetta e ciò che catch dichiara. Qualsiasi cosa tu voglia sollevare o gestire è una sottoclasse di Throwable. La classe stessa fornisce:

  • Un messaggio (getMessage())
  • Una stack trace catturata alla costruzione (getStackTrace(), printStackTrace())
  • Una causa opzionale — un altro Throwable che ha scatenato questo (getCause())
  • Eccezioni soppresse — errori secondari allegati dal try-with-resources (getSuppressed())

Quasi mai estendi Throwable direttamente. Il design interessante si trova un livello più in basso.

Error — non catturare

Error e le sue sottoclassi rappresentano fallimenti segnalati dalla JVM: memoria esaurita, stack overflow, un file di classe che non può essere collegato. Per convenzione non li si cattura nel codice applicativo, perché:

  1. Di solito significano che la JVM non è più affidabile. Continuare dopo un OutOfMemoryError raramente funziona a lungo.
  2. Quasi mai esiste un'azione di recupero sensata che il tuo codice possa intraprendere.
  3. La JVM stessa potrebbe già occuparsene; intercettare interferisce.

Error è tecnicamente catturabile — Java non te lo impedisce. Ma la convenzione è così forte che "cattura Error" è considerato un segnale d'allarme nelle code review. L'unico caso d'uso legittimo è un supervisore di primo livello (un request handler, un job runner) che registra l'errore ed esce in modo pulito.

Exception — fallimenti applicativi

Tutto ciò che si trova sotto Throwable tranne Error è Exception o uno dei suoi sottotipi. La linea tra checked e unchecked corre all'interno di questo ramo, non sopra di esso:

  • Le sottoclassi dirette di Exception che non sono RuntimeException sono checked.
  • RuntimeException e tutti i suoi sottotipi sono unchecked.

Ecco perché catch (Exception e) corrisponde sia a IOException (checked) sia a NullPointerException (unchecked) — sono fratelli sotto la stessa radice. È anche il motivo per cui catturare Exception è così generico: hai unito entrambi i rami insieme.

La distinzione checked/unchecked ha conseguenze reali per le firme dei tuoi metodi — le eccezioni checked devono essere dichiarate con throws o gestite, quelle unchecked no. Questo compromesso viene trattato separatamente in checked vs. unchecked exceptions.

RuntimeException — il ramo dei bug

RuntimeException e i suoi sottotipi sono riservati per convenzione agli errori di programmazione che non dovrebbero accadere nel codice corretto:

  • NullPointerException — dereferenziazione di null
  • IllegalArgumentException — argomento non valido
  • IllegalStateException — stato errato per l'operazione
  • IndexOutOfBoundsException — indice di lista/array oltre il limite
  • ArithmeticException — divisione per zero
  • ClassCastException — cast non valido
  • UnsupportedOperationException — operazione non supportata (es. modifica di una lista non modificabile)

Puoi lanciare queste eccezioni da qualsiasi punto senza cambiare la firma del metodo. I chiamanti possono catturarle, ma il linguaggio non li obbliga. Sono lo strumento giusto quando il fallimento dice "questo è un bug" anziché "questo a volte accade."

Relazioni di tipo nei catch

Un catch (T e) corrisponde a qualsiasi valore lanciato che sia un'istanza di T o un suo sottotipo. Quindi la gerarchia determina direttamente cosa vedono i tuoi catch:

try { ... }
catch (IOException e)        { ... }   // catches FileNotFoundException too
catch (Exception e)          { ... }   // catches almost everything below Throwable
catch (Throwable t)          { ... }   // catches everything, including Error — don't

L'ordine conta: poiché ogni catch corrisponde ai sottotipi, devi elencare i tipi più specifici per primi. Se catch (Exception e) venisse prima di catch (IOException e), il blocco IOException sarebbe irraggiungibile e il compilatore lo rifiuterebbe. Vedi multiple catch blocks per le regole complete.

Questo è anche il motivo per cui un pattern che cattura tutto è pericoloso. catch (Exception) cattura NullPointerException (un bug), IOException (un fallimento recuperabile) e IllegalStateException (probabilmente un bug) — tutto in un unico blocco, senza modo di gestirli diversamente. La gerarchia ti chiede di essere più specifico.

Ricercare i tipi

Quando incontri una nuova eccezione in una stack trace e vuoi sapere dove si trova:

  • Si trova in java.lang se è un errore fondamentale (NullPointerException, ArithmeticException).
  • Si trova in java.io, java.sql, java.net se è legata al dominio di quel pacchetto.
  • Una classe che termina con Error è quasi certamente sotto Error.
  • Una classe che termina con Exception è quasi certamente sotto Exception — ma verifica se estende RuntimeException per sapere se è checked.

Il Javadoc mostra la catena di ereditarietà in cima a ogni pagina. In caso di dubbio, consultalo.

Un esempio pratico

Un piccolo programma che percorre la gerarchia con controlli instanceof. Cattura una sequenza di lanci come Throwable, poi riporta dove si trova ciascuno nell'albero.

java— editable, runs on the server

Il metodo helper isChecked codifica la regola in una sola riga: il sottoinsieme checked è Exception meno RuntimeException. Esegui il programma e vedrai esattamente dove si trovano ciascuno dei cinque: IOException è checked, i due RuntimeException non lo sono, OutOfMemoryError è un Error (quindi non è né un Exception né checked), e il semplice Exception è checked.

Cosa fare dopo

L'albero built-in copre la maggior parte dei casi. Quando il tuo dominio ha fallimenti propri — "fattura non trovata," "configurazione non aggiornata" — scrivi le tue classi estendendo il nodo giusto di questo albero. Continua con Java custom exceptions.

Letture correlate:

Esercizio

Pratica
Un programma esegue `catch (Exception e)` attorno a un blocco. Quale di questi verrà catturato?
Un programma esegue `catch (Exception e)` attorno a un blocco. Quale di questi verrà catturato?
Was this page helpful?