Page d'accueilFindIt !ContactLes instructions et les opérateursLes threads

Le langage JavaTM

Table des matièresHierarchie des classes

CJava

 Les exceptions

throw, try, catch,...
La classe Throwable
Les exceptions Runtime
Les classes d'erreurs
Les autres exceptions

 

throw, try, catch,...

Pourquoi traiter des exceptions dès maintenant ? Parce qu'en Java, contrairement au C et au C++, les exceptions et leur traitement font partie intégrante du langage et sont utilisées systématiquement pour signaler toute erreur survenue pendant l'exécution d'une méthode (débordement d'indice d'un tableau, erreur d'accès à un fichier,...). De nombreuses méthodes sont susceptibles de déclencher (throw) des exceptions et donc il est impératif de savoir comment les traiter ou passer outre.
Une fois acquis le principe de cette forme de traitement d'erreur, vous pourrez utiliser les classes d'exceptions Java existantes ou créer vos propres classes pour traiter les erreurs qui peuvent survenir dans vos méthodes.

La gestion d'erreur par exceptions permet d'écrire de manière plus claire (donc plus maintenable) un programme, en isolant le traitement d'erreur de la suite d'instructions qui est exécutée si aucune erreur ne survient. Généralement, dans les langages ne disposant pas des exceptions (comme le C), les fonctions susceptibles de poser problème renvoient des valeurs que vous devez traiter immédiatement pour vérifier si aucune erreur n'est survenue.

C

La gestion des erreurs se fait grâce aux exceptions en Java. Donc, il n'existe pas de variables telles que errno... Les exceptions sont d'un abord plus difficile mais une fois compris le principe, la programmation des erreurs se fait plus simplement et "proprement".

C++

Les exceptions font partie du noyau du langage Java et leur gestion est obligatoire.

Syntaxe

En Java, cinq mots-clés servent à traiter les exceptions :

!

Chacune des instructions try, catch et finally doivent être suivi d'un bloc { ... } même si ce bloc ne comporte qu'une seule d'instruction (Désolé, les afficionados du code compact en seront pour leurs frais !).

 

Voici un exemple de traitement local d'une exception déclenchée grâce à throw dans un bloc try et interceptée par un des catch qui suivent :

class Classe0
{
  // ...
  void methode1 ()
  {
     try
     {
       // ...
       throw new Exception ();
     }
     catch (Exception exception1)
     {
       // Que faire en cas d'exception ?
     }
  }
}

Une méthode qui est susceptible de déclencher une exception peut s'écrire ainsi :

class Classe1
{
  // ...
  void methode1 () throws ClasseException
  {
    // ...
    // En cas d'erreur, déclenchement d'une exception
    throw new ClasseException ();
    // ...
  }
}

ClasseException est soit une classe prédéfinie dérivée de la classe Throwable, soit une classe dérivée d'une de ces classes créé par vous.

Quand vous appelez methode1 (), vous devez soit inclure l'appel à cette méthode dans un try ... catch, soit déclarer que la méthode qui appelle methode1 () est susceptible de déclencher une exception de la classe ClasseException, comme dans l'exemple suivant :

class Classe2
{
  Classe1 objet1 = new Classe1 ();
  // ...
  void methodeX ()
  {
     try
     {
        objet1.methode1 ();
        // ...
     }
     catch (ClasseException exception1)
     {
       // Que faire en cas de problème ?
     }
     // ... Eventuellement d'autres catch (...) 
     finally
     {
       // Le bloc finally est optionnel
       // Que faire après que le bloc try ou
       // qu'un bloc catch aient été exécutés ?
     }
  }
 
  void methodeY () throws ClasseException
  {
    objet1.methode1 ();
    // ...
  }
}

Le bloc finally est toujours exécuté, même si l'instruction return est exécutée dans les blocs try et catch : il sert à regrouper les instructions qu'il faut exécuter pour laisser dans un état correct votre programme qu'une exception est été déclenchée ou non. Par exemple, si le bloc try traite des accès à un fichier (ouverture, lecture/écriture,...), il est logique de fermer ce fichier dans le bloc finally, pour qu'il soit toujours finalement fermé.
Si une exception exception1 est déclenchée dans un bloc try et qu'aucun catch qui suit try n'intercepte exception1, alors le bloc finally est exécuté avant que le système ne continue à propager l'exception et trouve (ou non) un catch traitant les exceptions de la classe de exception1.
Si une exception exception2 est déclenchée dans un bloc catch, alors le bloc finally est aussi exécuté avant que le système ne continue à propager cette exception et trouve (ou non) un catch traitant les exceptions de la classe de exception2.

Chemin parcouru lors du traitement d'exceptions
figure 9. Chemin parcouru lors du traitement d'exceptions

La figure précédente illustre les chemins différents par lesquels peut passer le contrôle dans les méthodes methodeX () et methodeY (), suivant qu'une exception dans methode1 () est déclenchée (chemins vert et jaune) ou non (chemins rouge et bleu).

C

Afin de bien comprendre la gestion des erreurs avec les exceptions, voici un programme C typique traduit en Java où vous pourrez faire le parallèle entre constantes numériques façon C, et exception façon Java (à recopier dans un fichier EssaiException.java compilé avec l'instruction javac EssaiException.java, pour ensuite l'exécuter avec java ou Java Runner, grâce à l'instruction java EssaiException) :

/* Déclaration des constantes d'erreur */
#define ERR_OK        0
#define ERR_A_EGAL_B  1
 
 
 
 
 
 
 
 
int uneMethode (int a, int b)
 
{
  if (a == b)
    return ERR_A_EGAL_B;
  else
  {
    printf ("%d et %d OK !\n", a, b);
    return ERR_OK;
  }
}
 
int main ()
 
{
  int erreur;
  if ((erreur = uneMethode (1, 2))
              == ERR_OK)
    printf ("Pas d'erreur\n");
  else
    if (erreur == ERR_A_EGAL_B)
      printf ("Erreur\n");
}
 
// Déclaration d'une classe d'exception
class AEgalBException extends Exception
{
  public String toString ()
  {
    return "A égal à B !";
  }
}
 
public class EssaiException
{
  static void uneMethode (int a, int b)
               throws AEgalBException
  {
    if (a == b)
      throw new AEgalBException ();
    else
      System.out.println
           (a + " et " + b + " OK !");
  }
 
 
 
  public static void main
                  (String [ ] args)
  {
     try
     {
        uneMethode (1, 2);
        System.out.println ("Pas d'erreur");
     }
     catch (AEgalBException e)
     {
       System.out.println ("Erreur " + e);
     }
  }
}

 

Voici un autre exemple d'application dont la méthode newObject () permet de créer un objet en connaissant le nom de sa classe. Cette méthode simplifie le traitement des exceptions que peuvent déclencher les méthodes forName () et newInstance () de la classe Class en renvoyant une exception de classe IllegalArgumentException qu'il n'est pas obligatoire d'intercepter (à recopier dans un fichier InstantiationAvecNom.java compilé avec l'instruction javac InstantiationAvecNom.java pour ensuite l'exécuter avec java ou Java Runner, grâce à l'instruction java InstantiationAvecNom) :

public class InstantiationAvecNom
{
  // Methode transformant toutes les exceptions qui peuvent survenir 
  // pendant l'instanciation d'une classe en une exception IllegalArgumentException.
  // nomClasse doit indiquer un nom de classe avec son package
  public static Object newObject (String nomClasse)
  {
    try
    {
      return Class.forName (nomClasse).newInstance ();
    }
    catch (ClassNotFoundException e)
    {
      throw new IllegalArgumentException (
                  "La classe " + nomClasse + " n'existe pas");
    }
    catch (InstantiationException e)
    {
      throw new IllegalArgumentException (
                  "La classe " + nomClasse + " est abstract"
                  + " ou n'a pas de constructeur accessible par d\u00e9faut");
    }
    catch (IllegalAccessException e)
    {
      throw new IllegalArgumentException (
                  "La classe " + nomClasse + " n'est pas public");
    }
  }
 
  public static void main (String [ ] args)
  {
    // Essai avec différentes classes
    String nomsClasses [] = {"java.lang.Object",     // Ok
                             "java.lang.String",     // Ok
                             "java.lang.Integer",    // Pas de constructeur par défaut
                             "java.lang.Runnable"};  // Interface (= classe abstract)
    
    for (int i = 0; i < nomsClasses.length; i++)
      try
      {
        System.out.println (nomsClasses [i] + " : " + newObject (nomsClasses [i]));        
      }
      catch (IllegalArgumentException e)
      {
        System.out.println (e);
        System.out.println ("La classe " + nomsClasses [i]
                            + " ne peut etre instancie par Class.forName (\"" 
                            + nomsClasses [i] + "\").newInstance ();");
      }
  }
}
 
Autres exemples

Applets Chrono, ObservateurCalcul, EchoClient, PaperBoardClient, PlayApplet, CalculetteSimple, BoutonsNavigation, AnimationFleche, ScrollText et Horloge.
Applications LectureFichier, NumerotationLigne, ConcatenationFichiers, TestProtocole, HelloFromNet, EchoServer et PaperBoardServer.

Avantages des exceptions

Bien que d'un abord plus compliqué qu'une gestion d'erreur avec des constantes numériques, les exceptions comportent de nombreux avantages que vous percevrez à l'usage :

C++

L'équivalent de la clause catch (...) du C++ est catch (Throwable exception). En effet, toutes les classes d'exceptions Java héritent de la classe Throwable.

C++

La clause throw; qui permet de redéclencher une exception traitée dans un catch, a pour équivalent en Java throw exception;, où exception est l'exception reçu en paramètre par le catch.

C++

Java introduit le mot-clé throws qui permet de spécifier la liste des classes d'exceptions que peut déclencher une méthode, et que doit prendre en compte tout utilisateur de cette méthode (certains compilateurs C++ utilisent throw).

C++

Le traitement des exceptions en Java comporte une clause supplémentaire et optionnelle par rapport au C++ : l'instruction finally. Cette instruction permet de spécifier l'ensemble des instructions à exécuter une fois terminé le bloc d'instructions d'un try ou d'un des catch, qu'une exception ait été déclenchée ou non.


!

Soit methode1 () une méthode d'une classe Classe1, déclarant avec la clause throws une liste d'exception ClasseExceptionI qu'elle est susceptible de déclencher. Si methode1 () est outrepassée dans une classe Classe2 dérivée de Classe1, alors cette méthode ne peut déclarer que les exceptions ClasseExceptionI ou les exceptions dérivées de ClasseExceptionI (interdiction de déclencher des exceptions dont les classes ne sont pas liées à celles que peut déclencher la méthode outrepassée).
Bien sûr, ceci ne s'applique que pour les classes d'exceptions différentes de RuntimeException, Error et toutes leurs classes dérivées.
Voici un exemple vous montrant ceci :

abstract class Classe1
{
  abstract Class chercherClasse (String nomClasse);
}
 
class Classe2 extends Classe1
{
  // chercherClasse () est outrepassée
  Class chercherClasse (String nomClasse)
  {
     try
     {
        if (nomClasse.equals (""))
          throw new IllegalArgumentException ("Nom de classe vide");
       return Class.forName (nomClasse);
     }
     catch (ClassNotFoundException e)
     {
       // nomClasse pas trouvée : la méthode forName () de la classe
       // Class impose d'intercepter cette exception...
       throw new IllegalArgumentException ("Nom de classe inconnu");
     }
     // IllegalArgumentException est une classe dérivée de
     // RuntimeException donc il n'est pas obligatoire d'intercepter
     // les exceptions de cette classe
  }
 
  // Vous auriez pu décider de ne pas intercepter l'exception de
  // de class ClassNotFoundException et de déclarer par exemple :
  /* Class chercherClasse (String nomClasse) throws ClassNotFoundException
  {
    return Class.forName (nomClasse);
  }
  */
  // Ceci génère une erreur car il n'est pas possible d'outrepasser
  // chercherClasse () et de déclarer que cette méthode est susceptible de
  // déclencher des exceptions que chercherClasse () de Classe1 ne déclare pas...
}

Par contre, une méthode methode1 () outrepassant celle d'une super classe peut ne pas déclencher certaines des exceptions que la méthode outrepassée a déclarées dans sa clause throws, comme par exemple :

class Classe1
{
  void methode1 () throws Exception
  {
    // ...
    throw new Exception ();
  }
}
 
class Classe2 extends Classe1
{
  void methode1 ()
  {
    // ...
  }
}

Ceci peut être utile quand vous voulez outrepasser la méthode clone () de la classe Object dans une classe Classe1 pour permettre dans certains cas, de cloner les objets de la classe Classe1 sans avoir à intercepter l'exception CloneNotSupportedException :

class Classe1 implements Cloneable
{
  // ...
 
  // La méthode clone () de la classe Object peut déclencher 
  // une exception CloneNotSupportedException, mais ici
  // dans Classe1 clone () ne le fait pas
  public Object clone ()
  {
    try
    { 
      Classe1 clone = (Classe1)super.clone ();
      // ...
      return clone;
    }
    catch (CloneNotSupportedException e)
    {
      // Ne peut survenir car cette classe implémente Cloneable
      // mais obligation d'intercepter l'exception car la méthode clone () 
      // de la classe Object déclare qu'elle peut déclencher une exception 
      // de classe CloneNotSupportedException
      return null;
    } 
  }
}
 
class Classe2
{
  void methode (Classe1 objet1)
  {
    Classe1 objet2 = (Classe1)objet1.clone ();
    // ...
  }
}

La classe java.lang.Throwable

Les classes d'exceptions Java se divisent en plusieurs catégories. Elles héritent toutes de la classe Throwable décrite ci-dessous. Celle-ci n'est pas habituellement utilisée directement, mais toutes les exceptions héritent de ses méthodes, qui peuvent être intéressantes à utiliser ou à outrepasser.

Constructeurs

public Throwable ()
public Throwable (String message)

Allocation d'un nouvel objet Throwable, l'un sans message et l'autre avec message décrivant l'exception survenue. Une trace de l'état de la pile est automatiquement sauvegardé.

Méthodes

public String getMessage ()

Renvoie le message détaillé associé à l'objet.

public void printStackTrace ()
public void printStackTrace (PrintStream s)
Imprime sur la sortie standard ou sur un stream, l'exception et la trace de l'exception dans la pile.
public Throwable fillInStackTrace ()

Réinitialise la trace de la pile d'exécution. Cette méthode est utile uniquement quand vous voulez redéclencher une exception traitée par un catch, de la manière throw exception.fillInStackTrace ().

public String toString ()

Méthode outrepassée de la classe Object, renvoyant une description sommaire de l'exception

Les exceptions Runtime

Les catégories des exceptions Runtime (classe java.lang.RuntimeException et ses dérivées) et des classes d'erreurs (classe java.lang.Error et ses dérivées) sont spéciales : contrairement aux autres classes d'exceptions, un programme n'est pas obligé de traiter toutes les instructions pouvant déclencher ce type d'exceptions dans un try ... catch, et ceci essentiellement pour des raisons pratiques de programmation. En effet, en consultant la liste suivante vous vous rendrez compte que ces exceptions peuvent survenir très souvent dans un programme : Si le compilateur Java obligeait à prévoir un traitement en cas d'exception à chaque fois qu'une instruction peut déclencher une exception Runtime, votre programme aurait beaucoup plus de traitement d'exceptions que de code réellement utile.
De même, si vous voulez vous servir d'une de ces classes pour déclencher avec throw une exception dans une méthode methode1 (), vous n'êtes pas obligé de la déclarer dans la clause throws de methode1 ().
La classe RuntimeException dérive de la classe Exception. Voici la liste des exceptions dérivant de RuntimeException, qui sont susceptibles d'être déclenchées au cours de l'exécution d'un programme Java :

Les classes d'erreurs

Les classes d'erreurs dérivent de la classe Error, qui dérive elle-même de la classe Throwable. Elles sont généralement provoquée par la Machine Virtuelle Java à l'exécution, suite à un problème sur le chargement ou l'utilisation des classes, ou sur la Machine Virtuelle elle-même. Elles sont intéressantes à analyser quand elles sont déclenchées par un programme. Voici la liste de ces erreurs :

Les autres exceptions

Vous devez obligatoirement prendre en compte les exceptions dont la classe dérive de java.lang.Exception (sauf RuntimeException et ses classes dérivées), soit en les traitant dans un try ... catch, soit, grâce à la clause throws, en les ajoutant à la liste des classes d'exception susceptibles d'être renvoyées par une méthode. La classe Exception dérive de la classe Throwable. Les classes d'exceptions qui suivent sont déclenchées par certaines méthodes de la bibliothèque Java :


Page d'accueilFindIt !ContactLes instructions et les opérateursLes threadsDébut de la page
© Copyrights 1997-2023 Emmanuel PUYBARET / eTeks
- Tous droits réservés -
Table des matièresHiérarchie des classes