Java: blocchi catch multipli e multi-catch
Gestisci più tipi di eccezioni in Java con blocchi catch multipli o una clausola multi-catch (Tipo1 | Tipo2 e).
Un singolo try può essere seguito da qualsiasi numero di blocchi catch. Ognuno dichiara un tipo di eccezione diverso e viene eseguito solo se il lancio corrisponde. È così che Java permette a un blocco di codice di reagire diversamente a seconda di cosa è andato storto — un errore di rete non è un errore di analisi, e potresti volerli gestire in modi completamente diversi.
Catch sequenziali multipli
try {
String body = httpClient.get(url);
Config cfg = parser.parse(body);
apply(cfg);
} catch (IOException e) {
retryLater(url);
} catch (ParseException e) {
report("config file is malformed: " + e.getMessage());
} catch (SecurityException e) {
report("not allowed to apply config");
}Solo un catch viene eseguito per ogni lancio — il primo il cui tipo è una corrispondenza instance-of con l'eccezione lanciata. Dopo la sua esecuzione, il controllo salta all'istruzione dopo l'intero blocco try, non al catch successivo.
L'ordine è importante: dal più specifico al più generale
Un catch (T e) corrisponde a qualsiasi cosa che sia una T o qualsiasi sottoclasse di T. Se elenchi una superclasse prima della sua sottoclasse, il catch della sottoclasse diventa irraggiungibile e il compilatore lo rifiuta:
try { ... }
catch (Exception e) { ... } // matches everything below Exception
catch (IOException e) { ... } // ERROR: unreachableL'ordine da seguire è tipo più ristretto in cima, più ampio in fondo:
try { ... }
catch (FileNotFoundException e) { ... } // most specific
catch (IOException e) { ... } // wider
catch (Exception e) { ... } // catch-all (used sparingly)Questo è anche un utile esercizio di progettazione: scrivere i catch ti costringe a pensare a quali fallimenti il blocco può effettivamente produrre.
Multi-catch (un blocco, più tipi)
Java 7 ha introdotto la forma multi-catch: un singolo blocco che gestisce più tipi di eccezioni non correlati, separati da |:
try {
return parser.read(file);
} catch (IOException | ParseException e) {
log.warn("could not load config: " + e);
return Config.defaults();
}Usa questa forma quando la gestione è identica per più fallimenti distinti. È più concisa di due blocchi catch quasi duplicati e rende ovvia a colpo d'occhio la relazione "questi condividono una risposta".
Regole da conoscere:
- I tipi nell'unione non devono essere correlati per ereditarietà.
IOException | FileNotFoundExceptionnon compilerà — uno è un sottotipo dell'altro, quindi il più ampio lo copre già. - All'interno del blocco,
eè tipizzata come il supertipo comune dei tipi elencati. Puoi chiamare metodi dichiarati su quel supertipo, ma non quelli specifici del sottotipo. Per la maggior parte degli usi (logging, wrapping),getMessage()etoString()sono sufficienti. - Il parametro catch in un multi-catch è implicitamente final — non puoi riassegnarlo. (I catch a tipo singolo sono solo effettivamente final; la differenza non ha importanza in pratica.)
Quando separare un try
Un errore di leggibilità comune è avvolgere un intero metodo in un unico grande try, poi catturare tutto ciò che qualsiasi riga potrebbe lanciare. La logica di gestione in fondo diventa confusa riguardo a quale riga ha fallito.
Due strutture più pulite quando questo accade:
- Due istruzioni try separate, ciascuna limitata a un insieme correlato di operazioni.
- Un try all'interno di un metodo che chiama metodi più piccoli, ognuno responsabile di un tipo di fallimento.
Più piccolo è il try, più facili sono i catch da ragionare. "Quale riga potrebbe lanciare questo?" dovrebbe avere sempre una risposta breve.
Un esempio pratico
Un piccolo programma che fa tre cose — analizza un numero, lo cerca in un array, divide per esso — ognuna delle quali può fallire in modo diverso. Catturiamo ogni fallimento con un handler dedicato in modo che i messaggi rimangano specifici. L'ultimo catch usa la forma multi-catch per raggruppare due fallimenti che condividono una risposta.
Ogni input prende un percorso diverso attraverso i catch, ma ogni iterazione stampa una riga pulita — il programma non si arrende mai di fronte all'utente.
Cosa c'è dopo
try/catch gestisce il percorso felice e il percorso di fallimento. La terza clausola, finally, gestisce le cose che devono accadere in ogni caso. Continua con il blocco finally di Java.