Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Aspects complémentaires liés au traitement d'exceptions en Java

Comparaison try/finally VS try-with-resources Introduction à la généricité



Accès rapide :
La vidéo
La hiérarchie des classes d'exceptions
Erreurs, exceptions et « unchecked exceptions »
Quelques classes d'exceptions ou d'erreurs
Quelques méthodes utilisables sur les exceptions.
Utilisation du « multi-catch »
Utilisation du rethrow

La vidéo

Cette vidéo présente quelques aspects complémentaires liés au traitement d'exception en Java et notamment : la hiérarchie de classes d'exception, le multi-catch et le rethrow d'exception.


Mise en oeuvre d'une classe d'exception

La hiérarchie des classes d'exceptions

Erreurs, exceptions et « unchecked exceptions »

Comme vous vous en êtes aperçu dans les exemples précédents, il existe un certain nombre de classes d'exceptions prédéfinies. Pour être plus précis, les principales classes d'exceptions sont localisées sans le package java.lang (par exemple java.lang.ArithmeticException ou java.lang.SecurityException), mais ensuite, chaque package complète ces classes d'erreur avec ses exceptions propres. Par exemple le package java.io, pour la gestion des entrées/sorties, propose ses propres classes d'exceptions qui dériveront toutes de java.io.IOEXception.

Vous vous serez aussi aperçue que ces classes sont reliées les unes aux autres par des liens d'héritage. Le diagramme suivant présente la racine de cette hiérarchie de classe d'exceptions. On y voit très rapidement qu'il y a deux branches principales : les erreurs (dérivant de java.lang.Error et coloriées en rouge) et les exceptions (dérivant de java.lang.Exception, coloriées en vert et bleu). Ces deux types dérivant eux-mêmes de java.lang.Throwable.

Les erreurs (dérivant de java.lang.Error) représente des erreurs d'exécution dans la JVM. Autant vous dire que si vous interceptez ces types d'objets dans un bloc try / catch, vous ne pourrez pas y faire grand-chose. Les deux classes d'erreurs les plus connues sont java.lang.OutOfMemoryError (plus de possibilité de faire des new) et java.lang.StackOverflowError (vous avez saturé la pile d'exécution).

Les exceptions correspondent à des erreurs générées durant l'exemple de vos programmes, telles que des problèmes d'accès à des fichiers, ou autre. Notez néanmoins que j'ai coloré en vert la branche de classes dérivant de java.lang.RuntimeException : c'est ce qu'on appelle les « unchecked exceptions ». Ces exceptions, pouvant intervenir sur quasiment toutes vos lignes de code (par exemple, java.lang.NullPointerException), elles n'ont pas d'obligation d'être traitées et sont relayées à la méthode appelante par défaut.

Dans l'exemple relatif à la copie de fichier sans bloc catch, j'avais défini le mail ainsi :

 1 
 2 
 3 
 4 
 5 
public static String main( String [] args ) throws IOException {

    // Suite du main.

}
Exemple de définition d'un main pouvant produire des exceptions

Or, le constructeur de la classe FileInputStream peut aussi produire des exceptions de type java.lang.SecurityException. Alors pourquoi cette classe (qui ne dérive pas de java.io.IOException) n'est pas mentionnée dans la définition du main et pourquoi le programme compile sans erreur ?

La réponse est simple : par ce qu'elle dérive de java.lang.RuntimeException. Elle est donc remontée par défaut. Pour autant, la déclaration suivante serait légale.

 1 
 2 
 3 
 4 
 5 
public static String main( String [] args ) throws IOException, SecurityException {

    // Suite du main.

}
Exemple de définition d'un main pouvant produire des exceptions

Il est à noter que dans une Javadoc, les classes, les exceptions et les erreurs sont regroupées dans des catégories distinctes. Voici une capture d'écran montrant le contenu d'un package.

Regroupement des types d'un package

Quelques classes d'exceptions ou d'erreurs

Le tableau suivant vous présente quelques classes d'exceptions relativement courante que vous pourriez être amené à traiter.

Nom de la classe Description
java.lang.Throwable Classe de base de toutes erreurs ou toutes exceptions.
java.lang.Error Classe de base de toutes erreurs.
java.lang.OutOfMemoryError Vous avez saturé le tas (le heap en anglais) qui est l'espace mémoire dans lequel on produit les objets. La taille de cet espace peut être contrôlé via l'option -Xmx (MaXimum size) de la JVM.
java.lang.StackOverflowError Vous avez saturé la pile (la stack en anglais) qui est l'espace mémoire dans laquelle vos variables locales sont produites. La taille de cet espace peut être contrôlé via l'option -Xss (Stack Size) de la JVM.
java.lang.Exception Classe de base de toutes exceptions.
java.lang.RuntimeException Classe de base de toutes « unchecked exceptions ».
java.lang.ArithmeticException La division par zéro représente le cas le plus courant de déclenchement de cette exception.
java.lang.ArrayIndexOutOfBoundsException Une telle exception est déclenchée si votre indice dépasse des bornes du tableau
java.lang.ClassCastException Indique que votre cast n'est pas légal.
java.lang.NullPointerException Souvent abrégée en NPE, cette exception indique que vous avez manipulé un pointeur nul.
java.lang.SecurityException Indique qu'un problème de sécurité a été rencontré.
java.io.IOException Représente une erreur de manipulation d'un flux d'entrée/sortie.
java.net.SocketException Représente une erreur de manipulation d'une socket réseau. Cette classe dérive de java.io.IOException.
java.sql.SQLException Représente une erreur de manipulation d'une base de données relationnelle (basée sur SQL).

Quelques méthodes utilisables sur les exceptions.

La classe java.lang.Throwable expose un certain nombre de méthodes que vous pouvez utiliser sur n'importe quelle exception ou erreur. Le tableau suivant vous présente les principales méthodes.

Nom de la méthode Description
String getMessage(); Retourne le message de l'exception (sur une seule ligne)
StackTraceElement [] getStackTrace(); Retourne l'ensemble des appels (Stack Trace) ayant amené à produire cette exception
Throwable getCause(); Retourne l'erreur d'origine ayant produit l'erreur constatée. Effectivement, quand vous construisez une exception, il est possible d'indiquer au constructeur une exception d'origine.
void printStackTrace(); Affiche sur la sortie standard d'erreur le message de l'exception ainsi que la trace des appels de méthodes.
void printStackTrace( PrintStream outputStream ); Injecte, dans un flux 8 bits (dérivé de java.io.OutputStream) le message de l'exception ainsi que la trace des appels de méthodes.
void printStackTrace( PrintWriter writer ); Injecte, dans un flux 16 bits (dérivé de java.io.Writer) le message de l'exception ainsi que la trace des appels de méthodes.

Utilisation du « multi-catch »

Le Java SE 7.0 a apporté une facilité pour attraper plusieurs types d'exceptions avec un seul bloc catch : l'opérateur « multi-catch ». Bien sûr, vous pouvez capturer plusieurs types d'exceptions par polymorphisme, en utilisant un type de base. Mais souvent cela englobe trop de types d'exceptions. Le multi-catch pourra être bien plus précis.

Pour définir qu'un bloc catch correspond à plusieurs types d'exception, il faut utiliser l'opérateur | (touches ALT-GR + 6). Vous pouvez préciser autant de types d'exceptions que nécessaire. Voici un exemple d'utilisation.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
try {
    
    // Quelques lignes de code pouvant déclencher :
    // * soit une exception de type SQLException
    // * soit une exception de type IOException
    
} catch ( SQLException | IOException  exception ) {

    // On intercepte ici une eventuelle exception de type SQLException ou IOException 

}
Exemple d'utilisation du multi-catch
comme expliqué précédemment, on aurait pu aussi utiliser un bloc catch basé sur le type Exception, mais dans ce cas on aurait aussi intercepté les exceptions de type java.awt.AWTException, javax.naming.NamingException.html, java.awt.print.PrinterException, ... Le multi-catch cible donc mieux les exceptions à traiter.

Mais du coup, une question se pose : quels sont les membres (attributs, méthodes, ...) que l'on peut manipuler sur l'instance exception du bloc catch ? La réponse est simple, seuls les membres accessibles sur la première classe commune à nos exceptions seront utilisables. Concrètement, dans notre exemple, seuls les membres définis sur java.lang.Exception (la classe mère de nos deux types) seront accessibles. Parmi eux : toString, getMessage, printStackTrace, ...

Utilisation du rethrow

Le rethrow consiste à relancer une exception suite à son traitement partiel dans un bloc catch. Voici un premier exemple simple de rethrow.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
public void aMethod() throws Exception {
            
    try {
        
        // Faire quelque chose qui peut déclencher une exception
    
    } catch ( Exception exception ) {
    
        // Faire un traitement partiel de l'erreur.
        // puis relancer l'exception au niveau supérieur :
        throw exception;
    
    }
    
}
Un exemple de rethrow

Mais une nouvelle question se pose : si une instruction try / catch capture plusieurs types exceptions (via divers blocs catch, ou un « multi-catch ») et qu'on relance les exceptions interceptées, comment définir la signature de la méthode englobante ?

Une première solution consiste à utiliser le premier type parent commun aux diverses exceptions potentiellement attrapables. L'avantage de cette solution est que cela marche même avec un vieux Java SE (inférieur à 7.0). Voici un exemple.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
public void aMethod() throws Exception {
            
    try {
        
        // Faire quelque chose qui peut déclencher divers types d'exceptions
    
    } catch ( IOException exception ) {
    
        // Faire un traitement partiel de l'erreur.
        // puis relancer l'exception au niveau supérieur :
        throw exception;
    
    } catch ( SQLException exception ) {
    
        // Faire un autre traitement partiel de l'erreur.
        // puis relancer l'exception au niveau supérieur :
        throw exception;
    
    }
    
}
Le rethrow est la signature de la méthode englobante

Une autre solution, consiste à utiliser les nouvelles possibilités offertes à partir du Java SE 7.0. Il n'est plus obligatoire de typer la signature de la méthode via un type d'exception commun et l'on peut désormais lister tous les types d'exceptions potentiellement relancés. Voici un exemple de code.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
public void aMethod() throws IOException, SQLException {
            
    try {
        
        // Faire quelque chose qui peut déclencher divers types d'exceptions
    
    } catch ( IOException | SQLException exception ) {
    
        // Faire un traitement partiel de l'erreur.
        // puis relancer l'exception au niveau supérieur :
        throw exception;
    
    }
    
}
Le rethrow est la signature de la méthode englobante


Comparaison try/finally VS try-with-resources Introduction à la généricité