Hiérarchie des exceptions Java
Comment Throwable, Error, Exception et RuntimeException sont liés dans la hiérarchie des classes d'exceptions Java.
Chaque exception en Java fait partie d'un petit arbre de classes dont la racine est java.lang.Throwable. Connaître la forme de cet arbre est constamment utile : cela explique pourquoi catch (Exception e) n'attrape pas OutOfMemoryError, pourquoi RuntimeException est spécial, et pourquoi certaines exceptions vous obligent à les gérer alors que d'autres non. Tout l'ensemble tient en un seul schéma.
Cette page cartographie cet arbre : la racine Throwable, les branches Error et Exception, où se situe la frontière checked/unchecked, et comment tout cela détermine ce que vos blocs catch attrapent réellement.
L'arbre complet
Throwable
├── Error (unchecked — JVM-level)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ ├── VirtualMachineError
│ └── ...
└── Exception (checked by default)
├── IOException (checked)
├── SQLException (checked)
├── ClassNotFoundException (checked)
├── ...
└── RuntimeException (unchecked)
├── NullPointerException
├── IllegalArgumentException
├── IndexOutOfBoundsException
├── ArithmeticException
├── ClassCastException
├── IllegalStateException
└── ...L'ensemble de l'arbre est une seule hiérarchie de classes. C'est pourquoi un catch pour un supertype attrape ses sous-types, pourquoi les variables d'exception se comportent comme des références ordinaires, et pourquoi vous pouvez stocker une IOException dans un champ de type Exception.
Throwable — la racine
Throwable est ce que throw accepte et ce que catch déclare. Tout ce que vous souhaitez lever ou gérer est une sous-classe de Throwable. La classe elle-même fournit :
- Un message (
getMessage()) - Une trace de pile capturée à la construction (
getStackTrace(),printStackTrace()) - Une cause optionnelle — un autre
Throwablequi a déclenché celui-ci (getCause()) - Des exceptions supprimées — échecs secondaires attachés par try-with-resources (
getSuppressed())
Vous n'étendez presque jamais Throwable directement. La conception intéressante se trouve un niveau en dessous.
Error — ne pas attraper
Error et ses sous-classes représentent des défaillances que la JVM signale : mémoire insuffisante, débordement de pile, un fichier de classe qui ne peut pas être lié. Par convention, vous ne les attrapez pas dans le code applicatif, car :
- Elles signifient généralement que la JVM n'est plus fiable. Continuer après un
OutOfMemoryErrorfonctionne rarement longtemps. - Il n'existe presque jamais d'action de récupération sensée que votre code peut entreprendre.
- La JVM elle-même s'en occupe peut-être déjà ; les intercepter perturbe ce mécanisme.
Error est techniquement attrapable — Java ne vous en empêche pas. Mais la convention est si forte que « attraper Error » est considéré comme un signal d'alarme lors d'une revue de code. Le seul cas d'utilisation légitime est un superviseur de premier niveau (un gestionnaire de requêtes, un lanceur de tâches) qui journalise et se termine proprement.
Exception — les échecs applicatifs
Tout ce qui se trouve sous Throwable autre que Error est Exception ou l'un de ses sous-types. La frontière entre checked et unchecked passe à l'intérieur de cette branche, pas au-dessus :
- Les sous-classes directes de
Exceptionqui ne sont pasRuntimeExceptionsont checked. RuntimeExceptionet tous ses sous-types sont unchecked.
C'est pourquoi catch (Exception e) correspond à la fois à IOException (checked) et à NullPointerException (unchecked) — ce sont des frères sous la même racine. C'est aussi pourquoi attraper Exception est si grossier : vous avez regroupé les deux branches ensemble.
La distinction checked/unchecked a de vraies conséquences sur vos signatures de méthodes — les exceptions checked doivent être déclarées avec throws ou gérées, les unchecked non. Ce compromis fait l'objet d'un traitement dédié dans checked vs. unchecked exceptions.
RuntimeException — la branche des bugs
RuntimeException et ses sous-types sont réservés par convention aux erreurs de programmation qui ne devraient pas se produire dans un code correct :
NullPointerException— déréférencement de nullIllegalArgumentException— argument invalideIllegalStateException— état incorrect pour l'opérationIndexOutOfBoundsException— indice de liste/tableau dépassant la limiteArithmeticException— division par zéroClassCastException— cast invalideUnsupportedOperationException— opération non supportée (par exemple, mutation d'une liste non modifiable)
Vous pouvez les lever de n'importe où sans modifier votre signature de méthode. Les appelants peuvent les attraper, mais le langage ne les y oblige pas. Ce sont les bons outils quand l'échec signifie « c'est un bug » plutôt que « cela arrive parfois ».
Relations de types dans catch
Un catch (T e) correspond à toute valeur levée qui est une instance de T ou d'un sous-type de T. La hiérarchie dicte donc directement ce que vos blocs catch voient :
try { ... }
catch (IOException e) { ... } // catches FileNotFoundException too
catch (Exception e) { ... } // catches almost everything below Throwable
catch (Throwable t) { ... } // catches everything, including Error — don'tL'ordre est important ici : comme chaque catch correspond aux sous-types, vous devez lister les types les plus spécifiques en premier. Si catch (Exception e) venait avant catch (IOException e), le bloc IOException serait inaccessible et le compilateur le rejetterait. Consultez multiple catch blocks pour les règles complètes.
C'est aussi pourquoi une forme attrape-tout est dangereuse. catch (Exception) attrape NullPointerException (un bug) et IOException (un échec récupérable) et IllegalStateException (probablement un bug) — le tout dans un seul bloc, sans possibilité de les gérer différemment. La hiérarchie vous invite à être plus précis.
Rechercher des types
Quand vous rencontrez une nouvelle exception dans une trace de pile et voulez savoir où elle se situe :
- Elle est dans
java.langs'il s'agit d'une erreur fondamentale (NullPointerException,ArithmeticException). - Elle est dans
java.io,java.sql,java.netsi elle concerne le domaine de ce package. - Une classe se terminant par
Errorse trouve presque certainement sousError. - Une classe se terminant par
Exceptionse trouve presque certainement sousException— mais vérifiez si elle étendRuntimeExceptionpour savoir si elle est checked.
La Javadoc affiche la chaîne d'héritage en haut de chaque page. En cas de doute, consultez-la.
Un exemple concret
Un petit programme qui parcourt la hiérarchie avec des vérifications instanceof. Il attrape une séquence de levées comme Throwable, puis indique où chacune se situe dans l'arbre.
L'auxiliaire isChecked encode la règle en une seule ligne : le sous-ensemble checked correspond à Exception moins RuntimeException. Exécutez le programme et vous verrez exactement où se situent chacun des cinq : IOException est checked, les deux RuntimeException ne le sont pas, OutOfMemoryError est une Error (donc ni une Exception ni checked), et la Exception simple est checked.
La suite
L'arbre intégré couvre la plupart des cas. Quand votre domaine a ses propres échecs — « facture introuvable », « configuration obsolète » — vous écrivez vos propres classes en étendant le bon nœud de cet arbre. Continuez vers Java custom exceptions.
Lectures connexes :
- Try-catch basics — comment
catchsélectionne réellement un gestionnaire. throwetthrows— lever des exceptions et déclarer les checked.- Checked vs. unchecked — la frontière à l'intérieur de la branche
Exception. - Exception best practices — quoi faire avec ce que vous attrapez.