W3docs

Eccezioni in Java

Panoramica delle eccezioni in Java: cosa sono, la gerarchia e perché la gestione delle eccezioni è importante.

Un'eccezione è ciò che accade quando il programma si trova in una situazione che non riesce a gestire nel percorso corrente — un file che non esiste, una divisione per zero, un indice di array fuori dai limiti. Invece di restituire un risultato errato o corrompere silenziosamente lo stato, Java interrompe il metodo corrente, crea un oggetto eccezione che descrive il problema e inizia a cercare del codice in grado di gestirlo. Imparare il meccanismo delle eccezioni significa imparare come Java ti comunica cosa è andato storto, dove, e cosa potresti fare al riguardo.

Cosa è realmente un'eccezione

Un'eccezione è un normale oggetto Java. In particolare, un'istanza di una classe che eredita da java.lang.Throwable. Quando qualcosa va storto, la JVM (o il codice stesso) crea uno di questi oggetti e lo lancia. Da quel momento, il programma segue un flusso di controllo diverso finché un blocco catch accetta l'eccezione o il thread termina.

L'oggetto contiene informazioni: un tipo (la classe — NullPointerException, IOException, ecc.), un messaggio (una descrizione leggibile dall'utente) e uno stack trace (la catena di chiamate congelata nel momento in cui l'eccezione è stata creata). Quando leggi un'eccezione in una console, stai leggendo queste tre cose.

Exception in thread "main" java.lang.ArithmeticException: / by zero
  at calc.Money.divide(Money.java:42)
  at calc.App.main(App.java:11)

La prima riga è tipo + messaggio. Le righe rientrate sono lo stack trace, dalla chiamata più recente alla più vecchia.

Perché le eccezioni invece dei codici di errore

I linguaggi più vecchi — C è l'esempio classico — segnalano il fallimento con codici di ritorno: ogni funzione restituisce un int, lo controlli, e se è negativo qualcosa è andato storto. Questo approccio ha due problemi:

  1. Puoi ignorare il valore di ritorno. Un chiamante che dimentica di controllarlo non vede alcun fallimento immediato, e il bug emerge più tardi in un punto confuso.
  2. Il percorso di errore intralcia il percorso principale. Ogni riga di lavoro reale è avvolta in if (err) return err;.

Le eccezioni invertono questo. Il comportamento predefinito è che un'eccezione non gestita interrompe l'esecuzione, rumorosamente. Puoi scegliere di gestirla dove hai effettivamente una strategia. Il percorso principale rimane pulito; il percorso di recupero è nel suo blocco.

Le tre cose che possono andare storte

Java ordina tutto ciò che è Throwable in tre categorie, e la differenza è importante perché il linguaggio le tratta diversamente:

  • Error — la JVM stessa è in difficoltà. OutOfMemoryError, StackOverflowError. Non le catturi nel codice normale. Di solito non c'è nulla di utile che puoi fare.
  • RuntimeException (una sottoclasse di Exception) — bug di programmazione che emergono a runtime. NullPointerException, IndexOutOfBoundsException, ClassCastException. Il compilatore non ti obbliga a gestirle, perché in codice corretto non dovrebbero accadere.
  • Exception controllata (tutto il resto sotto Exception) — fallimenti recuperabili che il programma dovrebbe anticipare. IOException, SQLException. Il compilatore richiede che tu le catturi o le dichiari nella clausola throws del tuo metodo.

Il confine tra "bug di programmazione" (eccezione runtime) e "fallimento anticipato" (eccezione controllata) è una delle decisioni progettuali più discusse di Java. Torneremo su questo in Eccezioni controllate e non controllate in Java.

Come funzionano lancio e cattura

Quando viene eseguito un throw, la JVM:

  1. Svolge lo stack — il metodo corrente termina bruscamente, poi lo fa il suo chiamante, e così via.
  2. Per ogni frame, controlla la presenza di un blocco try { ... } catch (SomeType e) { ... } il cui catch corrisponde al tipo dell'eccezione (o a un supertipo).
  3. Il primo catch corrispondente vince. Il controllo salta lì, lo svolgimento dello stack si interrompe e l'esecuzione riprende nel blocco catch.
  4. Se nulla corrisponde, il thread termina. In un programma a thread singolo, ciò significa che la JVM stampa lo stack trace ed esce.

Ecco perché un'eccezione lanciata può attraversare molti metodi prima di essere catturata — ogni lancio non gestito risale di un frame.

Un primo assaggio

Non è necessario scrivere throw per incontrare un'eccezione — Java stesso le lancia quando qualcosa va storto. Ecco l'esempio più semplice: dividere un intero per zero. La JVM crea un ArithmeticException per te.

java— editable, runs on the server

Senza il try/catch, la terza iterazione farebbe crashare l'intero programma e la quarta non verrebbe mai eseguita. Con esso, il fallimento è contenuto: riceviamo un messaggio di errore e il ciclo continua. Questa capacità — di limitare il danno di un fallimento — è l'intero scopo del meccanismo delle eccezioni.

Cosa tratta questa parte del libro

I capitoli rimanenti in questa parte costituiscono il vocabolario operativo della gestione delle eccezioni in Java, un elemento alla volta:

  • La struttura di try/catch/finally e lo scopo di ogni clausola.
  • Catch multipli e la scorciatoia multi-catch.
  • try-with-resources per tutto ciò che deve essere chiuso.
  • Lanciare eccezioni con throw e dichiararle con throws.
  • Controllate vs. non controllate: quale usare e quando.
  • La gerarchia completa delle classi.
  • Scrivere tipi di eccezione personalizzati per il tuo dominio.
  • I principi che distinguono una buona gestione delle eccezioni dal rumore difensivo.

Leggilo in ordine — ogni capitolo presuppone la comprensione del precedente.

Cosa c'è dopo

La gestione delle eccezioni inizia con il costrutto più fondamentale: il blocco try/catch. Continua con Java try...catch.

Practice

Pratica
Un metodo `loadConfig()` legge un file e lancia `IOException` in caso di errore. Il chiamante racchiude la chiamata in `try { loadConfig(); } catch (RuntimeException e) { ... }`. L'operazione IO fallisce. Cosa succede?
Un metodo `loadConfig()` legge un file e lancia `IOException` in caso di errore. Il chiamante racchiude la chiamata in `try { loadConfig(); } catch (RuntimeException e) { ... }`. L'operazione IO fallisce. Cosa succede?
Was this page helpful?