Java try...catch
Gestisci gli errori in Java con i blocchi try...catch per mantenere il programma in esecuzione quando qualcosa va storto.
try/catch è l'unità minima di gestione delle eccezioni in Java. Si racchiude il codice rischioso in try e lo si segue con uno o più blocchi catch che specificano cosa fare se viene lanciata una determinata eccezione. Insieme formano un'unica istruzione; non è possibile avere l'uno senza l'altro (o senza un finally, che vedremo in un capitolo successivo).
Struttura
try {
// code that might throw
} catch (ExceptionType e) {
// code that runs only if a matching exception was thrown
}Alcune cose che all'inizio è facile trascurare:
- La variabile nel
catch(eper convenzione) è circoscritta al blocco catch. Puoi chiamarla come vuoi;eedexsono le scelte più comuni. - Il parametro del catch è effettivamente final — puoi leggerlo ma non dovresti riassegnarlo. Trattarlo come immutabile rende il codice più facile da seguire.
- Il blocco
tryha un proprio scope. Le variabili dichiarate al suo interno non sono visibili nelcatchné dopo l'istruzione. Se hai bisogno di un valore calcolato neltryin seguito, dichiaralo fuori.
String content;
try {
content = Files.readString(path);
} catch (IOException e) {
content = ""; // works because `content` was declared outside the try
}
System.out.println(content);Quali catch corrispondono
Un blocco catch (T e) viene eseguito quando l'eccezione lanciata è un'istanza di T — incluso qualsiasi sottotipo di T. Quindi:
catch (Exception e)cattura quasi tutto:IOException,NullPointerException, le tue eccezioni personalizzate.catch (RuntimeException e)cattura i bug a runtime ma non le eccezioni verificate comeIOException.catch (NullPointerException e)cattura solo le NPE (e le sottoclassi, che in genere non esistono).
All'interno del try, viene eseguito solo il primo catch corrispondente. Se elenchi un tipo più generico prima di uno più specifico, il catch più specifico è irraggiungibile e il compilatore rifiuta il codice:
try { ... }
catch (Exception e) { ... } // matches everything
catch (IOException e) { ... } // ERROR: unreachableElenca sempre i catch dal più specifico al più generale.
Cosa fare in un catch
Un blocco catch non è un posto dove far sparire le eccezioni. È un posto dove decidere cosa fare riguardo a un fallimento noto. Le opzioni realistiche sono:
- Recuperare — riprovare, ricorrere a un valore predefinito, passare a una risorsa diversa. Questo è il caso migliore, ed è più raro di quanto si pensi.
- Registrare e rilanciare — registrare i dettagli e lasciare che l'eccezione continui a propagarsi verso un gestore di livello superiore che sa cosa fare.
- Avvolgere e rilanciare — tradurre un'eccezione di basso livello in una che si adatta al vocabolario di questo livello (
IOException→ConfigLoadException). - Tradurre in un valore di ritorno — quando il fallimento è genuinamente atteso (ad esempio durante il parsing dell'input utente), restituire
Optional.empty()o un valore sentinella.
La cosa da non fare è catturare un'eccezione e continuare silenziosamente senza registrarla né agire su di essa. È così che i bug scompaiono nel nulla. Torneremo su questo nel capitolo sulle best practice.
Leggere le informazioni sull'eccezione
Il parametro del catch è un oggetto. Tre metodi che userai costantemente:
e.getMessage()— la descrizione leggibile dall'utente. Può esserenull.e.toString()— nome della classe più messaggio, ad es.java.io.IOException: file not found.e.printStackTrace()— scrive la traccia suSystem.err. Comodo durante il debug, ma usa un vero logger nel codice di produzione (instrada la traccia attraverso lo stesso canale di tutto il resto).
Un quarto metodo, e.getCause(), restituisce l'eccezione sottostante quando una è stata avvolta — utile quando occorre comprendere il fallimento originale all'interno di un livello di traduzione.
Catturare Exception vs. catturare Throwable
Puoi scrivere catch (Throwable t) e catturerà tutto — inclusi gli Error come OutOfMemoryError. Non farlo. Gli Error significano che la JVM è in difficoltà e il tuo codice non è in grado di reagire sensatamente a essi. Catturare Throwable maschera bug che dovrebbero far terminare il processo.
Attieniti a Exception o a uno dei suoi sottotipi. Se hai davvero bisogno di registrare l'imprevisto prima di lasciarlo propagare, la forma corretta è catch (RuntimeException e) { log; throw; }.
Un esempio pratico
Una piccola utility che analizza un intero da ogni riga di input. Alcune righe contengono numeri validi, altre no, una è null. Usiamo try/catch per proseguire oltre le righe errate e contare quanto è successo. Chiamare line.trim() lancia una NullPointerException sull'elemento null, mentre Integer.parseInt lancia una NumberFormatException su tutto ciò che non è un intero — due tipi di eccezione distinti, ciascuno con il proprio catch.
L'output è:
not an integer: "hello"
null line — skipped
not an integer: "3.14"
---
parsed: 3, failed: 3, sum: 118
Due cose da notare. Prima, il programma termina. Senza i catch, la prima riga errata avrebbe interrotto l'esecuzione e nulla dopo di essa sarebbe stato contato. Seconda, i due fallimenti sono distinti: la riga null prende il ramo NullPointerException e stampa il proprio messaggio, mentre "hello" e "3.14" prendono il ramo NumberFormatException. Selezionare i catch per tipo è il modo per evitare che fallimenti diversi si confondano tra loro.
Cosa c'è dopo
Un solo catch è il caso più semplice. Il codice reale di solito deve gestire più fallimenti diversi contemporaneamente. Continua con i blocchi catch multipli e il multi-catch in Java. Dopo di ciò, il blocco finally e try-with-resources completano la sintassi, mentre throw e throws coprono l'altro lato: segnalare i fallimenti dal proprio codice.