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:
- 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.
- 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 diException) — bug di programmazione che emergono a runtime.NullPointerException,IndexOutOfBoundsException,ClassCastException. Il compilatore non ti obbliga a gestirle, perché in codice corretto non dovrebbero accadere.Exceptioncontrollata (tutto il resto sottoException) — fallimenti recuperabili che il programma dovrebbe anticipare.IOException,SQLException. Il compilatore richiede che tu le catturi o le dichiari nella clausolathrowsdel 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:
- Svolge lo stack — il metodo corrente termina bruscamente, poi lo fa il suo chiamante, e così via.
- Per ogni frame, controlla la presenza di un blocco
try { ... } catch (SomeType e) { ... }il cuicatchcorrisponde al tipo dell'eccezione (o a un supertipo). - Il primo
catchcorrispondente vince. Il controllo salta lì, lo svolgimento dello stack si interrompe e l'esecuzione riprende nel blocco catch. - 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.
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/finallye lo scopo di ogni clausola. - Catch multipli e la scorciatoia multi-catch.
try-with-resourcesper tutto ciò che deve essere chiuso.- Lanciare eccezioni con
throwe dichiararle conthrows. - 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.