W3docs

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 :

  1. 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.
  2. 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 de Exception) — 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.
  • Exception contrôlée (tout le reste sous Exception) — 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 clause throws de 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 :

  1. Déroule la pile — la méthode en cours se termine brusquement, puis son appelant fait de même, et ainsi de suite.
  2. À chaque frame, elle vérifie l'existence d'un bloc try { ... } catch (SomeType e) { ... } dont le catch correspond au type de l'exception (ou à un supertype).
  3. Le premier catch correspondant 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.
  4. 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.

java— editable, runs on the server

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/finally et le rôle de chaque clause.
  • Les captures multiples et le raccourci multi-catch.
  • try-with-resources pour tout ce qui doit être fermé.
  • Lancer des exceptions vous-même avec throw, et les déclarer avec throws.
  • 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.

Pratique

Pratique
Une méthode `loadConfig()` lit un fichier et lance `IOException` en cas d'échec. L'appelant enveloppe l'appel dans `try { loadConfig(); } catch (RuntimeException e) { ... }`. L'IO échoue. Que se passe-t-il ?
Une méthode `loadConfig()` lit un fichier et lance `IOException` en cas d'échec. L'appelant enveloppe l'appel dans `try { loadConfig(); } catch (RuntimeException e) { ... }`. L'IO échoue. Que se passe-t-il ?
Was this page helpful?