IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Développons en Java v 2.20   Copyright (C) 1999-2021 Jean-Michel DOUDOUX.   
[ Précédent ] [ Sommaire ] [ Suivant ] [ Télécharger ]      [ Accueil ] [ Commentez ]


 

7. La gestion des exceptions

 

chapitre   7

 

Niveau : niveau 2 Elémentaire 

 

Les exceptions représentent le mécanisme de gestion des erreurs intégré au langage Java. Il se compose d'objets représentant les erreurs et d'un ensemble de trois mots clés qui permettent de détecter et de traiter ces erreurs (try, catch et finally ) mais aussi de les lever ou les propager (throw et throws).

Lors de la détection d'une erreur, un objet qui hérite de la classe Exception est créé (on dit qu'une exception est levée) et propagé à travers la pile d'exécution jusqu'à ce qu'il soit traité.

Ces mécanismes permettent de renforcer la sécurité du code Java.

Exemple : une exception levée à l'exécution non capturée
public class TestException {
  public static void main(java.lang.String[] args) {
    int i = 3;
    int j = 0;
    System.out.println("résultat = " + (i / j));
  }
}

Résultat :
C:>java TestException
Exception in thread "main" java.lang.ArithmeticException: / 
by zero
        at tests.TestException.main(TestException.java:23)

Si dans un bloc de code on fait appel à une méthode qui peut potentiellement générer une exception, on doit soit essayer de la récupérer avec try/catch, soit ajouter le mot clé throws dans la déclaration du bloc. Si on ne le fait pas, il y a une erreur à la compilation. Les erreurs et exceptions du paquetage java.lang échappent à cette contrainte. Throws permet de déléguer la responsabilité des erreurs à la méthode appelante

Ce procédé présente un inconvénient : de nombreuses méthodes des packages java indiquent dans leur déclaration qu'elles peuvent lever une exception. Cependant ceci garantit que certaines exceptions critiques seront prises explicitement en compte par le programmeur.

Ce chapitre contient plusieurs sections :

 

7.1. Les mots clés try, catch et finally

Le bloc try rassemble les appels de méthodes susceptibles de produire des erreurs ou des exceptions. L'instruction try est suivie d'instructions entre des accolades.

Exemple ( code Java 1.1 ) :
    try {
      operation_risquée1;
      opération_risquée2;
    } catch (ExceptionInteressante e) {
      traitements
    } catch (ExceptionParticulière e) {
      traitements
    } catch (Exception e) {
      traitements
    } finally {
      traitement_pour_terminer_proprement;
    }

Si un événement indésirable survient dans le bloc try, la partie éventuellement non exécutée de ce bloc est abandonnée et le premier bloc catch est traité. Si un bloc catch est défini pour capturer l'exception issue du bloc try alors elle est traitée en exécutant le code associé au bloc. Si le bloc catch est vide (aucune instruction entre les accolades) alors l'exception capturée est ignorée. Une telle utilisation de l'instruction try/catch n'est pas une bonne pratique : il est préférable de toujours apporter un traitement adapté lors de la capture d'une exception.

S'il y a plusieurs types d'erreurs et d'exceptions à intercepter, il faut définir autant de blocs catch que de types d'événements. Par type d'exception, il faut comprendre « qui est du type de la classe de l'exception ou d'une de ses sous-classes ». Ainsi dans l'ordre séquentiel des clauses catch, un type d'exception ne doit pas venir après un type d'une exception d'une super-classe. Il faut faire attention à l'ordre des clauses catch pour traiter en premier les exceptions les plus précises (sous-classes) avant les exceptions plus générales. Un message d'erreur est émis par le compilateur dans le cas contraire.

Exemple ( code Java 1.1 ) : erreur à la compil car Exception est traité en premier alors que ArithmeticException est une sous-classe de Exception
public class TestException {
  public static void main(java.lang.String[] args) {
    // Insert code to start the application here.
    int i = 3;
    int j = 0;
    try {
      System.out.println("résultat = " + (i / j));
    } catch (Exception e) {
    } catch (ArithmeticException e) {
    }
  }
}

Résultat :
C:\tests>javac TestException.java
TestException.java:11: catch not reached.
        catch (ArithmeticException e) {
        ^
1 error

Si l'exception générée est une instance de la classe déclarée dans la clause catch ou d'une classe dérivée, alors on exécute le bloc associé. Si l'exception n'est pas traitée par un bloc catch, elle sera transmise au bloc de niveau supérieur. Si l'on ne se trouve pas dans un autre bloc try, on quitte la méthode en cours, qui regénère à son tour une exception dans la méthode appelante.

L'exécution totale du bloc try et d'un bloc d'une clause catch sont mutuellement exclusives : si une exception est levée, l'exécution du bloc try est arrêtée et si elle existe, la clause catch adéquate est exécutée.

La clause finally définit un bloc qui sera toujours exécuté, qu'une exception soit levée ou non. Ce bloc est facultatif. Il est aussi exécuté si dans le bloc try il y a une instruction break ou continue.

 

7.2. La classe Throwable

Cette classe descend directement de la classe Object : c'est la classe de base pour le traitement des erreurs.

Cette classe possède deux constructeurs :

Méthode Rôle
Throwable()  
Throwable(String) La chaîne en paramètre permet de définir un message qui décrit l'exception et qui pourra être consulté dans un bloc catch.

Les principales méthodes de la classe Throwable sont :

Méthodes Rôle
String getMessage( ) lecture du message
void printStackTrace( ) affiche l'exception et l'état de la pile d'exécution au moment de son appel
void printStackTrace(PrintStream s) Idem mais envoie le résultat dans un flux

Exemple ( code Java 1.1 ) :
public class TestException {
  public static void main(java.lang.String[] args) {
    // Insert code to start the application here.
    int i = 3;
    int j = 0;
    try {
      System.out.println("résultat = " + (i / j));
    } catch (ArithmeticException e) {
      System.out.println("getmessage");
      System.out.println(e.getMessage());
      System.out.println(" ");
      System.out.println("toString");
      System.out.println(e.toString());
      System.out.println(" ");
      System.out.println("printStackTrace");
      e.printStackTrace();
    }
  }
}

Résultat :
C:>java TestException
getmessage
/ by zero

toString
java.lang.ArithmeticException: / by zero

printStackTrace
java.lang.ArithmeticException: / by zero
        at tests.TestException.main(TestException.java:24)

 

7.3. Les classes Exception, RunTimeException et Error

Ces trois classes descendent de Throwable : en fait, toutes les exceptions dérivent de la classe Throwable.

hierarchie d'exceptions

La classe Error représente une erreur grave intervenue dans la machine virtuelle Java ou dans un sous système Java. L'application Java s'arrête instantanément dès l'apparition d'une exception de la classe Error.

La classe Exception représente des erreurs moins graves. Les exceptions héritant de la classe RuntimeException n'ont pas besoin d'être détectées impérativement par des blocs try/catch.

 

7.4. Les exceptions personnalisées

Pour générer une exception, il suffit d'utiliser le mot clé throw, suivi d'un objet dont la classe dérive de Throwable. Si l'on veut générer une exception dans une méthode avec throw, il faut l'indiquer dans la déclaration de la méthode, en utilisant le mot clé throws.

En cas de nécessité, on peut créer ses propres exceptions. Elles descendent des classes Exception ou RunTimeException mais pas de la classe Error. Il est préférable (par convention) d'inclure le mot « Exception » dans le nom de la nouvelle classe.

Exemple ( code Java 1.1 ) :
public class SaisieErroneeException extends Exception {

  public SaisieErroneeException() {
    super();
  }

  public SaisieErroneeException(String s) {
    super(s);
  }
}

public class TestSaisieErroneeException {
  public static void controle(String chaine) throws SaisieErroneeException {
    if (chaine.equals("") == true)
      throw new SaisieErroneeException("Saisie erronee : chaine vide");
  }
  
  public static void main(java.lang.String[] args) {
    String chaine1 = "bonjour";
    String chaine2 = "";
    
    try {
      controle(chaine1);
    } catch (SaisieErroneeException e) {
      System.out.println("Chaine1 saisie erronee");
    }
    
    try {
      controle(chaine2);
    } catch (SaisieErroneeException e) {
      System.out.println("Chaine2 saisie erronee");
    }
  }
}

Les méthodes pouvant lever des exceptions doivent inclure une clause throws nom_exception dans leur en-tête. L'objectif est double : avoir une valeur documentaire et préciser au compilateur que cette méthode pourra lever cette exception et que toute méthode qui l'appelle devra prendre en compte cette exception (traitement ou propagation).

Si la méthode appelante ne traite pas l'erreur ou ne la propage pas, le compilateur génère l'exception nom_exception must be caught or it must be declared in the throws clause of this method.

Java n'oblige à déclarer les exceptions dans l'en-tête de la méthode que pour les exceptions dites contrôlées (checked). Les exceptions non contrôlées (unchecked) peuvent être capturées mais n'ont pas à être déclarées. Les exceptions et erreurs qui héritent de RunTimeException et de Error sont non contrôlées. Toutes les autres exceptions sont contrôlées.

 

7.5. Les exceptions chaînées

Il est fréquent durant le traitement d'une exception de lever une autre exception. Pour ne pas perdre la trace de l'exception d'origine, Java propose le chaînage d'exceptions pour conserver l'empilement des exceptions levées durant les traitements.

Il y a deux façons de chaîner deux exceptions :

  • Utiliser la surcharge du constructeur de Throwable qui attend un objet Throwable en paramètre
  • Utiliser la méthode initCause() d'une instance de Throwable
Exemple :
package com.jmdoudoux.test;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class TestExceptionChainee {

  public static void main(String[] args) {
    try {
      String donnees = lireFichier();
      System.out.println("donnees=" + donnees);
    } catch (MonException e) {
      e.printStackTrace();
    }
  }

  public static String lireFichier() throws MonException {
    File fichier = new File("c:/tmp/test.txt");
    FileReader reader = null;

    StringBuffer donnees = new StringBuffer();

    try {
      reader = new FileReader(fichier);
      char[] buffer = new char[2048];
      int len;
      while ((len = reader.read(buffer)) > 0) {
        donnees.append(buffer, 0, len);
      }
    } catch (IOException e) {
      throw new MonException("Impossible de lire le fichier", e);
    } finally {
      try {
        if (reader != null) {
          reader.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    return donnees.toString();
  }
}

Résultat :
com.jmdoudoux.test.MonException: Impossible de lire le fichier
        at com.jmdoudoux.test.TestExceptionChainee.lireFichier(TestExceptionChainee.java:33)
        at com.jmdoudoux.test.TestExceptionChainee.main(TestExceptionChainee.java:12)
Caused by: java.io.FileNotFoundException: c:\tmp\test.txt (The system cannot 
        find the path specified)
        at java.io.FileInputStream.open(Native Method)
        at java.io.FileInputStream.<init>(FileInputStream.java:106)
        at java.io.FileReader.<init>(FileReader.java:55)
        at com.jmdoudoux.test.TestExceptionChainee.lireFichier(TestExceptionChainee.java:26)
        ... 1 more

La méthode getCause() héritée de Throwable permet d'obtenir l'exception originale.

Exemple :
  public static void main(
      String[] args) {
    try {
      String donnees = lireFichier();
      System.out.println("donnees=" + donnees);
    } catch (MonException e) {
      // e.printStackTrace();
      System.out.println(e.getCause().getMessage());
    }
  }

Résultat :
c:\tmp\test.txt (The system cannot find the path specified)

 

7.6. L'utilisation des exceptions

Il est préférable d'utiliser les exceptions fournies par Java lorsqu'une de ces exceptions répond au besoin plutôt que de définir sa propre exception.

Il existe trois types d'exceptions :

  • Error : ces exceptions concernent des problèmes liés à l'environnement. Elles héritent de la classe Error (exemple : OutOfMemoryError)
  • RuntimeException : ces exceptions concernent des erreurs de programmation qui peuvent survenir à de nombreux endroits dans le code (exemple : NullPointerException). Elles héritent de la classe RuntimeException
  • Checked exception : ces exceptions doivent être traitées ou propagées. Toutes les exceptions qui n'appartiennent pas aux catégories précédentes sont de ce type

Les exceptions de type Error et RuntimeException sont dites unchecked exceptions car les méthodes n'ont pas d'obligation à les traiter ou à déclarer leur propagation explicitement. Ceci se justifie par le fait que leur levée n'est pas facilement prédictible.

Il n'est pas recommandé de créer ses propres exceptions en dérivant d'une exception de type unchecked (classe de type RuntimeException). Même si cela peut sembler plus facile puisqu'il n'est pas obligatoire de déclarer leur propagation, cela peut engendrer certaines difficultés, notamment :

  • oublier de traiter cette exception
  • ne pas savoir que cette exception peut être levée par une méthode.

Cependant, l'utilisation d'exceptions de type unchecked se répend de plus en plus notamment depuis la diffusion de la plate-forme .Net qui ne propose que ce type d'exceptions.

 


Développons en Java v 2.20   Copyright (C) 1999-2021 Jean-Michel DOUDOUX.   
[ Précédent ] [ Sommaire ] [ Suivant ] [ Télécharger ]      [ Accueil ] [ Commentez ]