Les exceptions Java
Vue d'ensemble des exceptions Java — ce qu'elles sont, la hiérarchie des exceptions et pourquoi la gestion des exceptions est importante.
Une exception est ce qui se produit lorsque votre programme rencontre une situation qu'il ne peut pas gérer dans son chemin d'exécution actuel — un fichier introuvable, une division d'un nombre par zéro, un index de tableau hors limites. Au lieu de renvoyer une mauvaise réponse ou de corrompre silencieusement l'état, Java interrompt la méthode en cours, crée un objet exception décrivant ce qui s'est passé, et commence à chercher du code capable de gérer la situation. Apprendre le mécanisme des exceptions, c'est apprendre comment Java vous indique ce qui s'est passé, où et ce que vous pouvez faire.
Ce qu'est réellement une exception
Une exception est un objet Java ordinaire. Plus précisément, une instance d'une classe qui hérite de java.lang.Throwable. Lorsque quelque chose tourne mal, la JVM (ou votre propre code) crée l'un de ces objets et le lance. À partir de ce moment, le programme suit un flux de contrôle différent jusqu'à ce qu'un bloc catch accepte l'exception ou que le thread se termine.
L'objet contient des informations : un type (la classe — NullPointerException, IOException, etc.), un message (une description lisible par un humain) et une trace de pile (la chaîne d'appels figée au moment où l'exception a été créée). Lorsque vous lisez une exception dans une console, vous lisez ces trois éléments.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at calc.Money.divide(Money.java:42)
at calc.App.main(App.java:11)La première ligne est le type + le message. Les lignes indentées constituent la trace de pile, avec l'appel le plus récent en premier.
Pourquoi les exceptions plutôt que les codes d'erreur
Les langages plus anciens — C en est l'exemple classique — signalent les échecs par des codes de retour : chaque fonction renvoie un entier, vous le vérifiez, et s'il est négatif, quelque chose s'est mal passé. Cette approche présente deux problèmes :
- Vous pouvez ignorer la valeur de retour. Un appelant qui oublie de la vérifier ne voit aucun échec immédiat, et le bug se manifeste plus tard à un endroit déroutant.
- Le chemin d'erreur encombre le chemin nominal. Chaque ligne de vrai travail est enveloppée dans
if (err) return err;.
Les exceptions inversent cela. Par défaut, une exception non gérée stoppe l'exécution, de façon visible. Vous choisissez de la gérer là où vous avez réellement une stratégie. Le chemin nominal reste propre ; le chemin de récupération est dans son propre bloc.
Les trois types de problèmes possibles
Java classe tout ce qui est Throwable en trois catégories, et la différence est importante car le langage les traite différemment :
Error— la JVM elle-même est en difficulté.OutOfMemoryError,StackOverflowError. Vous n'interceptez pas ces erreurs dans du code ordinaire. Il n'y a généralement rien d'utile à faire.RuntimeException(une sous-classe deException) — des bugs de programmation qui apparaissent à l'exécution.NullPointerException,IndexOutOfBoundsException,ClassCastException. Le compilateur ne vous force pas à les gérer, car dans un code correct, ils ne devraient pas se produire.Exceptioncontrôlée (tout le reste sousException) — des échecs récupérables que le programme devrait anticiper.IOException,SQLException. Le compilateur exige que vous les interceptiez ou que vous les déclariez dans la clausethrowsde votre méthode.
La frontière entre « bug de programmation » (exception d'exécution) et « échec anticipé » (exception contrôlée) est l'une des décisions de conception les plus débattues en Java. Nous y reviendrons dans Java checked vs. unchecked exceptions.
Comment fonctionnent le lancement et l'interception
Lorsqu'un throw s'exécute, la JVM :
- Déroule la pile — la méthode en cours se termine brusquement, puis son appelant fait de même, et ainsi de suite.
- À chaque frame, elle vérifie l'existence d'un bloc
try { ... } catch (SomeType e) { ... }dont lecatchcorrespond au type de l'exception (ou à un supertype). - Le premier
catchcorrespondant l'emporte. Le contrôle y est transféré, le déroulement de la pile s'arrête, et l'exécution reprend dans le bloc catch. - Si rien ne correspond, le thread se termine. Dans un programme mono-thread, cela signifie que la JVM affiche la trace de pile et se ferme.
C'est pourquoi une exception lancée peut traverser plusieurs méthodes avant d'être interceptée — chaque lancement non intercepté remonte d'un niveau supplémentaire.
Un premier aperçu
Vous n'avez pas besoin d'écrire throw pour rencontrer une exception — Java lui-même en lance lorsque quelque chose tourne mal. Voici l'exemple le plus simple : diviser un entier par zéro. La JVM crée une ArithmeticException pour vous.
Sans le try/catch, la troisième itération ferait planter tout le programme et la quatrième ne s'exécuterait jamais. Avec lui, l'échec est contenu : on obtient un message d'erreur et la boucle continue. Cette capacité — à limiter les dégâts d'un échec — est tout l'intérêt du mécanisme d'exceptions.
Ce que couvre cette partie du livre
Les chapitres suivants de cette partie constituent le vocabulaire de travail de la gestion des exceptions Java, pièce par pièce :
- La structure de
try/catch/finallyet le rôle de chaque clause. - Les captures multiples et le raccourci multi-catch.
try-with-resourcespour tout ce qui doit être fermé.- Lancer des exceptions vous-même avec
throw, et les déclarer avecthrows. - Contrôlée vs. non contrôlée : laquelle utiliser et quand.
- La hiérarchie complète des classes.
- Écrire vos propres types d'exceptions pour votre domaine.
- Les principes qui distinguent une bonne gestion des exceptions du bruit défensif.
Lisez-les dans l'ordre — chaque chapitre suppose que le précédent a été lu.
La suite
La gestion des exceptions commence par la construction la plus fondamentale : le bloc try/catch. Continuez avec Java try...catch.