2. Erreurs et Exceptions

2.1. Classe Throwable, notion de stack trace

Les erreurs et exceptions sont en fait des objets Java (tout est objet en Java, donc cette phrase n'apporte pas beaucoup d'information). Ces objets appartiennent à des classes qui étendent toutes, directement ou indirectement, la classe Throwable (package java.lang). Cette classe est immédiatement étendue par trois autres classes :
  • Exception
  • RuntimeException
  • Error
Les exceptions qui étendent Error et RuntimeException sont les unechecked exception , pour lesquels il n'est pas besoin d'écrire de code pour les gérer. En revanche les exceptions qui n'étendent aucune de ces deux classes, qui étendent donc directement Throwable ou Exception sont les checked exceptions qui doivent être gérées dans un bloc try / catch ou une clause throws . Un objet de type Throwable est construit à partir de deux éléments : un message d'erreur, censé indiquer la nature de l'exception, et une autre exception, censée être la cause première de la génération de cette exception. Ces deux éléments sont facultatifs, on peut aussi générer une exception à partir de rien. La classe Throwable comporte un jeu de méthodes qui permettent de gérer ce que l'on appelle la stack trace d'une exception. Cette stack trace représente la pile d'appel du programme au moment où l'exception a été générée. Il s'agit d'une liste de noms de classes, assortis d'un numéro de ligne, qui indique très précisément à quel endroit du programme l'erreur a été produite. L'exploitation de cette stack trace est primordiale en phase de mise au point des logiciels, et permet le plus souvent de corriger très rapidement les bugs que l'on ne manque pas d'ajouter dans son code. Voyons dès maintenant un exemple de stack trace , relevé lors du démarrage (ou tout du moins de la tentative de démarrage) d'un serveur Tomcat.
 Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/util/logging/LogFactory  
     at org.apache.catalina.startup.Bootstrap.<clinit>(Bootstrap.java:54)  
 Caused by: java.lang.ClassNotFoundException: org.apache.util.logging.LogFactory  
     at java.net.URLClassLoader$1.run(URLClassLoader.java:217)  
     at java.security.AccessController.doPrivileged(Native Method)  
     at java.net.URLClassLoader.findClass(URLClassLoader.java:205)  
     at java.lang.ClassLoader.loadClass(ClassLoader.java:323)  
     at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294)  
     at java.lang.ClassLoader.loadClass(ClassLoader.java:268)  
     at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:336)</clinit>  
L'analyse d'une stack trace procède toujours de la même méthode. La première ligne donne le message d'erreur : ici la machine Java se plaint de ne pas trouver la classe org/apache/util/logging/LogFactory. Il s'agit probablement d'un fichier JAR, nécessaire lors de l'exécution de Tomcat, introuvable dans le classpath de la machine Java. Cette erreur a été constatée ligne 54 de la classe org.apache.catalina.startup.Bootstrap. Suit la cause de cette erreur : une exception de type ClassNotFoundException, rencontrée dans la classe URLClassLoader. Cette partie de l'exception n'est pas très intéressante ici, dans la mesure où il s'agit de classes internes à l'API Java. Une exception pour une classe non trouvée intervient toujours dans un class loader, puisque c'est lui qui est chargé de fournir les classes dont nos applications ont besoin. Ce qui nous intéresse c'est l'endroit de notre application qui a demandé cette classe, et cet endroit nous est fourni par la première partie de la stack trace . Il arrive toutefois que la situation soit inverse, d'où l'intérêt de posséder les deux parties de la stack trace. Notons que la cause d'une exception est elle-même une exception, qui peut avoir elle-même sa propre cause. Cette notion de cause est primordiale, et on le voit dans notre exemple : on peut propager en tant que cause une exception rencontrée dans une couche basse d'une application. Cette première exception va probablement en générer d'autres dans d'autres couches, et grâce à ce mécanisme, on peut conserver la stack trace de l'exception primaire, et ainsi faciliter le déverminage de l'application. Ce qu'il est important de comprendre, c'est qu'une stack trace est créée au moment où l'objet Throwable est instancié, à partir de la pile d'appel du thread (nous n'avons pas encore vu les threads dans ce cours, disons pour l'instant qu'il s'agit du programme qui s'exécute) dans lequel l'erreur a été constatée. Il est donc très important de préserver cette exception lors des mécanismes de propagation, si l'on ne veut pas perdre cette information précieuse. Notons enfin que la classe Throwable implémente Serializable, propriété qui se transmet donc à toute la hiérarchie.

2.2. Classe Error

La classe Error modélise des erreurs d'exécution d'une application que l'on ne gère pas. Citons par exemple : la saturation de la mémoire ( OutOfMemoryError) ou le dépassement de capacité de la pile d'appel ( StackOverflowError). La génération d'une telle erreur signifie que l'application est vraiment très malade. Sa fin de vie est en général assez proche.

2.3. Classe RuntimException

La classe RuntimException modélise des erreurs d'exécution d'une application que l'on ne gère pas non plus, mais qui ont un statut différent. Elles signifient qu'une opération non prévisible a eu lieu. Par exemple l'appel d'une méthode au travers d'un pointeur nul, qui va générer la bien connue NullPointerException. Autre exemple : la division par zéro ( ArithmeticException) ou la lecture d'un tableau au-delà de sa limite ( ArrayIndexOutOfBoundsException). Ce genre de choses n'est pas censé arriver dans une application normalement constituée.

2.4. Classe Exception

À la différence de la classe Error et la classe RuntimeException, la classe Exception modélise les erreurs d'exécution que l'on doit prévoir. Parmi elles : l'impossibilité d'ouvrir un fichier ou de se connecter à une ressource réseau. Ces erreurs sont prévisibles, et le développeur doit proposer un comportement si elles interviennent. La classe Exception n'ajoute aucune méthode à la classe Throwable.
Java langage & API
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
Introduction : un peu d'histoire
1. Java : genèse d'un nouveau langage
Programmer en Java
1. Un premier exemple
1.1. Aperçu général, cycle de vie
1.2. Un premier programme
1.3. Programmes, applets, servlets, etc...
2. Une première classe
2.1. Écriture d'une classe
2.2. Instanciation de cette classe
3. Types de base, classes et objets
3.1. Types de base
3.2. Classes et objets
Classes Object et String
1. Introduction
2. La classe Object
2.1. La méthode toString()
2.2. La méthode clone()
2.3. La méthode equals()
2.4. La méthode hashCode()
2.5. La méthode finalize()
2.6. La méthode getClass()
3. La classe String
3.1. Introduction
3.2. Construction d'un objet de type String
3.3. Concaténation, StringBuffer et StringBuilder
3.4. Concaténations de chaînes de caractères depuis Java 8
3.5. Concaténations de chaînes de caractères depuis Java 11
3.6. Extraction d'une sous-chaîne de caractères
3.7. Comparaison de deux chaînes de caractères
3.8. Méthodes de comparaisons lexicographiques
3.9. Méthode de recherche de caractères
3.10. Méthode de modification de chaîne
3.11. Méthode de duplication
3.12. Support de l'unicode, internationalisation
Structure d'une classe
1. Introduction
2. Classes
2.1. Classes publiques
2.2. Classes internes
2.3. Classe membre
2.4. Classes locales
2.5. Classes anonymes
2.6. Le mot-clé this
3. Éléments statiques
3.1. Champ statique
3.2. Cas des constantes
3.3. Bloc statique
3.4. Classe membre statique
4. Membres d'une classe, visibilité
4.1. Bloc non statique
4.2. Accès à un membre, visibilité
4.3. Les champs
4.4. Signature d'une méthode
4.5. Les méthodes
4.6. Getters et Setters
5. Constructeur, instanciation
5.1. Chargement d'une classe
5.2. Constructeurs d'une classe
5.3. Instanciation d'un objet
5.4. Destruction d'objets
5.5. Le mot-clé final
6. Énumérations
6.1. Déclaration d'une énumération
6.2. Classe énumération
6.3. Méthode toString()
6.4. Méthode valueOf()
6.5. Méthode values()
6.6. Méthode ordinal()
6.7. Méthode compareTo()
6.8. Constructeurs privés
6.9. Classe utilitaire : EnumSet
Noms, opérateurs, tableaux
1. Introduction
2. Identificateurs, noms et expressions
2.1. Identificateurs et noms
2.2. Expressions
3. Opérateurs, ordre d'exécution
3.1. Ordre d'exécution
3.2. Les opérateurs
3.3. Les opérateurs ++ et --
3.4. Les opérateurs % et /
3.5. Les opérateurs <<, >> et >>>
3.6. L'opérateur instanceof
3.7. Les opérateurs &, | et ^
3.8. Les opérateurs && et ||
3.9. L'opérateur ? ... :
3.10. Les opérateurs d'affectation
4. Tableaux
4.1. Création d'un tableau
4.2. Initialisation d'un tableau
4.3. Utilisation d'un tableau comme un Object
4.4. Tableaux de tableaux
4.5. Copie de tableaux
5. Blocs, boucles et contrôles
5.1. Blocs
5.2. Mots-clés réservés
5.3. Tests : if et switch
5.4. Boucles : for, while, do ... while
5.5. Commandes continue et break
5.6. Commandes return et goto
Nombres, précision, calculs
1. Introduction
2. Calculs
2.1. Précision
2.2. Codage des nombres flottants
2.3. Le mot-clé strictfp
2.4. Conversion de types
3. Dépassements de capacité
3.1. Cas des entiers
3.2. Cas des flottants
3.3. Bibliothèques BigInteger et BigDecimal
4. Fonctions mathématiques
4.1. Fonctions usuelles
4.2. Générateurs aléatoires
5. Classes enveloppe
5.1. Associer les types de base à des objets
5.2. Auto-boxing
Héritage, abstraction, interfaces
1. Introduction
2. Abstraction et encapsulation
2.1. Abstraction
2.2. Encapsulation
3. Héritage
3.1. Définition de l'héritage
3.2. Conséquences pour les membres
3.3. Polymorphisme
3.4. Empêcher l'héritage
4. Classes abstraites
5. Interfaces
5.1. Introduction
5.2. Définition
5.3. Java 8 et les interfaces
5.4. Utilisation des interfaces
5.5. Définition de constantes dans les interfaces
5.6. Utilité des interfaces
Packages
1. Introduction
2. Notion de paquet
2.1. Déclaration d’appartenance à un paquet
2.2. Chargement d’une classe
2.3. Choix de nom
3. Archives, chemin de recherche, classpath
3.1. Archives
3.2. Variable CLASSPATH
3.3. Notion de classloader
3.4. Bilan sur les classes chargées
3.5. Visibilité
3.6. Conseils d'écriture, bibliothèque standard
Exceptions
1. Introduction
2. Erreurs et Exceptions
2.1. Classe Throwable, notion de stack trace
2.2. Classe Error
2.3. Classe RuntimException
2.4. Classe Exception
3. Déclenchement d'une exception
3.1. Exceptions déclenchées par la JVM
3.2. Exceptions déclenchées par l'application
4. Capter une exception
4.1. Traiter une exception localement
4.2. Code de captage
5. Créer ses propres exceptions
Entrées / sorties
1. Introduction
2. Notion de fichier
2.1. Introduction
2.2. La classe File
2.3. Construction d'une instance de File
2.4. Méthodes exposées
3. Flux de sortie
3.1. Introduction, notion de flux
3.2. Écriture de caractères, classe Writer
3.3. Bufferisation, construction d'un flux sur un autre
3.4. Utilisation de PrintWriter
3.5. Écriture d'octets, OuputStream
3.6. Écriture de types primitifs : DataOutputStream
3.7. Écriture d'objets : ObjectOutputStream
4. Flux d'entrée
4.1. Introduction
4.2. Lecture de caractères, classe Reader
4.3. Bufferisation, lecture ligne par ligne
4.4. Lecture d'octets : InputStream
4.5. Lecture de types primitifs : DataInputStream
4.6. Lecture d'objets : ObjectInputStream
5. Lecture et écriture de flux croisés
5.1. Introduction
5.2. Lire des caractères sur un flux binaire : classe InputStreamReader
5.3. Écrire des caractères sur un flux binaire : classe OutputStreamWriter
5.4. Remarque sur les jeux de caractères
6. Serialization d'objets
6.1. Enjeu de la sérialization d'objets
6.2. Serialization d'un objet
6.3. Sérialization d'une grappe d'objets
6.4. Première surcharge : méthode writeObject() readObject()
6.5. Deuxième surcharge : utilisation d'un externalizer
6.6. Troisième surcharge : utilisation d'un objet proxy
7. Flux compressés
7.1. Introduction
7.2. Flux de type gzip
7.3. Flux de type zip