IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Penser en Java  -  Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières  -  Thinking in Java

  Chapitre 10 - Gestion des erreurs avec les exceptions

pages : 1 2 3

La philosophie de base de Java est que « un code mal formé ne sera pas exécuté ».

L'instant idéal pour détecter une erreur est la compilation, bien avant que vous essayiez d'exécuter le programme. Malheureusement, toutes les erreurs ne peuvent être détectées à cette étape. Les problèmes restants doivent être gérés à l'exécution avec un certain formalisme qui permet à l'initiateur de l'erreur de passer une information suffisante à un récepteur qui va savoir comment traiter proprement cette anomalie.

En C et dans d'autres langages plus anciens, il pouvait y avoir plusieurs formalismes, et ils étaient généralement établis comme des conventions et pas comme partie intégrante du langage de programmation. la gestion des erreurs se faisant typiquement en retournant par exemple une valeur spécifique ou en positionnant un drapeau [flag], le récepteur avait la charge de tester ces données et de déterminer qu'il y avait eu une anomalie. Malgré cela au fil des ans on se rendit compte que les programmeurs qui utilisent des librairies avaient tendance à se croire infaillible « oui des erreurs peuvent se produire chez les autres mais pas dans mon code ! » Alors, de façon quasi naturelle, ils ne souhaitaient pas tester les conditions d'erreur (elles étaient parfois trop grossières pour être testées [51]. Si vous étiez assez méticuleux pour tester toutes les conditions d'erreur à chaque appel de fonction votre code pouvait devenir d'une illisibilité cauchemardesque. Parce que les programmeurs avaient toujours la possibilité de coupler un système avec ces langages ils étaient réticents à admettre la vérité : cette approche de la gestion des erreurs était un obstacle à la création de grands programmes, robustes et faciles à maintenir.

La solution est prendre la nature primitive de la gestion des erreurs et de renforcer le formalisme. Cette solution à une longue histoire, puisque l'implémentation de la gestion des erreurs remonte au systèmes d'exploitation des années soixante et même au « ON ERROR GOTO » du BASIC. Mais la gestion des erreurs du C++ était basée sur ADA, et Java est basé sur le C++ (bien qu'il ressemble plus a du pascal objet).

Le « Mot » exception est pris dans le sens « je fais exception de ce cas ». A l'instant ou le problème se produit vous pouvez ne pas savoir quoi en faire mais vous savez que vous ne pouvez pas continuer sans vous en soucier ; vous devez vous arrêter et quelqu'un quelque part doit savoir que faire. Donc vous transmettez le problème dans un contexte plus général ou quelqu'un est qualifié pour prendre la décision appropriée (comme dans une chaîne de commandement).

L'autre apport significatif de la gestion des exceptions est qu'elle simplifie le code de la gestion des erreurs. Au lieu de tester une condition d'erreur et la traiter à différents endroits de votre programme, vous n'avez plus besoin de placer le code de gestion des erreurs à l'appel de la méthode (puisque l'exception garantira que quelqu'un la lèvera) et vous avez juste besoin de traiter le problème à un seul endroit, appelé Exception Handler. Cette gestion vous permet d'économiser du code, et de séparer le code que vous voulez exécuter du code qui doit être exécuté quand des erreurs se produisent. Généralement, la lecture l'écriture et le déboguage du code est plus claire avec les exceptions qu'avec l'ancienne méthode.

La gestion des exceptions étant supportée par le Compilateur JAVA, il y a de nombreux exemples qui peuvent être écrits dans ce livre sans apprendre le mécanisme de gestion des exceptions. Ce chapitre vous instruit sur le code dont vous avez besoin pour gérer de façon adéquate les exceptions, et la façon de générer vos propres exceptions si une de vos méthodes venait à ne pas fonctionner.

Les exceptions de base

Une condition exceptionnelle est un problème qui interdit de continuer la méthode ou la partie de code que vous êtes en train d'exécuter. il est important de distinguer une condition exceptionnelle d'un problème normal. Avec une condition exceptionnelle vous ne pouvez pas continuer l'exécution car vous n'avez pas suffisamment d'informations pour la traiter dans le contexte courant. Tout ce que vous pouvez faire est de changer de contexte et d'essayer de régler ce problème dans un contexte de plus haut niveau. C'est ce qui se passe quand vous générez une exception.

Un exemple simple est la division. Si vous vous apprêtez à diviser par Zéro, il est intéressant de vérifier que le dénominateur est non nul avant d'effectuer la division. Mais qu'est ce que cela signifie que le dénominateur soit égal à zéro ? Peut être savez vous, dans le contexte, de cette méthode particulière comment agir avec un dénominateur égal à Zéro. Mais si c'est une valeur inattendue, vous ne pouvez pas la traiter et vous devez donc générer une exception plutôt que de continuer l'exécution.

Quand vous générez une exception, plusieurs actions sont déclenchées. Premièrement, l'objet Exception est instancié de la même façon que tout objet java est crée : à la volée avec l'instruction new. Ensuite le chemin courant d'exécution (celui que vous ne pouvez continuer) est stoppé et la référence à l'objet exception est sortie du contexte courant. A cet instant le mécanisme des gestion des exceptions prend la main et commence à chercher un endroit approprié pour continuer à exécuter le programme. Cet endroit est le Gestionnaire d'exception (Exception Handler), dont la tâche est de résoudre le problème de façon à ce que le programme puisse essayer de lancer ou une autre tâche ou juste continuer en séquence.

Pour prendre un exemple simple, considérons une instance d'objet appelée t. Il est possible qu'il vous soit passé une référence qui n'ai pas été initialisée, donc vous pouvez vouloir vérifier son initialisation avant d'appeler une méthode sur cette instance. Vous pouvez envoyer l'information sur l'erreur dans un contexte plus général en créant un objet représentant votre information et en la lançant depuis votre contexte. Cette action s'appelle générer une exception. Cette action ressemble à ceci :

if(t == null)
  throw new NullPointerException();


Cette chaîne de caractères peut être extraite ultérieurement de différentes façons, nous verrons cela brièvement.

Le mot-clé throw déclenche une série d'événements relativement magiques. Typiquement, vous allez d'abord utilisez new pour instancier un objet qui représente la condition d'erreur. Vous transmettez la référence résultante au throw. L'objet est en effet retourné par la méthode, même si ce n'est pas ce type d'objet que la méthode doit renvoyer. Une vision simpliste de la gestion des exceptions est de la considérer comme un mécanisme de retour mais vous allez au devant de problèmes si vous poursuivez cette analogie trop loin. Vous pouvez aussi sortir de l'exécution normale en lançant une exception. Mais une valeur est retournée et la méthode ou l'environnement se termine.

Toute similitude avec un retour ordinaire d'une méthode s'arrête ici parce que l'endroit vous arrivez est complètement différent de celui d'un retour normal d'une méthode. (Vous atterrissez chez le gestionnaire d'exception approprié qui peut être très éloigné, plusieurs niveau plus bas sur la pile d'appels, de l'endroit ou l'exception a été générée.)

De plus, vous pouvez lancer n'importe quel type d'objet Throwable. Ainsi vous générerez une classe d'exception pour chaque type d'erreur. l'information à propos de l'erreur est contenue à la fois dans l'objet exception et implicitement dans le type de l'objet exception choisi, afin que quelqu'un dans le contexte supérieur sache quoi faire de votre exception (Souvent, la seule information est le type de l'objet exception rien d'autre de significatif est stocké.)

Attraper une exception

Si une méthode génère une exception elle doit supposer qu'elle sera intercepté et levée. Un des avantages de la gestion des exceptions dans Java est qu'elle vous permet de vous concentrer sur le problème que vous essayez de résoudre à un endroit, et enfin de placer le code concernant les erreurs à un autre endroit.

Pour comprendre comment une exception est levée vous devez comprendre le concept de région surveillée qui est une région de code qui peut générer des exceptions et qui est suivie par le code qui traite ces exceptions.

Le bloc try

Si vous êtes à l'intérieur d'une méthode et que vous générez une exception (ou une méthode à l'intérieur de celle ci génère une exception), cette méthode va sortir dans le processus de génération de l'exception. Si vous ne voulez pas qu'un throw provoque la sortie de la méthode vous pouvez spécifier un bloc spécial à l'intérieur de celle-ci qui va intercepter l'exception. C'est le Bloc try appelé ainsi car vous essayez vos différents appels de méthode ici . Le bloc try est une section ordinaire précédé du mot clé try.


try {
  // Code that might generate exceptions
}

Si vous vouliez tester les erreurs attentivement dans un langage de programmation qui ne supporte pas la gestion des exceptions vous devriez entourez l'appel de chaque méthode avec le code de vérification de l'initialisation et de vérification des erreurs, même si vous appeliez la même méthode plusieurs fois. Avec la Gestion des exceptions vous mettez tout dans le bloc try et capturer toute les exceptions en un seul endroit. Cela signifie que votre code est beaucoup plus simple à lire et à écrire car le bon code est séparé de celui de la gestion des erreurs.

Les gestionnaires d'exceptions


Bien sûr, l'exception générée doit être traitée quelque part. Cet endroit est le gestionnaire d'exceptions, et il y en a un pour chaque type d'exception que vous voulez intercepter. Les gestionnaires d'exceptions sont placés juste derrière le bloc try. Et reconnaissables par le mot clé catch.

try {
  // Code that might generate exceptions
} catch(Type1 id1) {
  // Handle exceptions of Type1
} catch(Type2 id2) {
  // Handle exceptions of Type2
} catch(Type3 id3) {
  // Handle exceptions of Type3
}
// etc...

 Chaque clause du catch (gestionnaire d'exception) est comme une méthode qui ne prend qu'un argument d'un type particulier. Les identifiants (id1,id2, et ainsi de suite) peuvent êtres utilisés dans les gestionnaire d'exception, comme des paramètres de méthodes. Quelque fois vous n'utilisez pas l'identifiant car le type de l'exception vous fournit assez d'informations pour la traiter mais il doit toujours être présent.
 Le gestionnaire doit être placé juste derrière le bloc try. Si une exception est générée, le mécanisme de gestion des exceptions va à la recherche du premier gestionnaire d .exception dont le type correspond à celui de l'exception, cette recherche se termine quand une des clauses Catch est exécutée. Seulement une clause catch sera exécutée ; ce n'est pas comme un switch ou vous devez positionner un break après chaque case pour empêcher les autres conditions de s'exécuter.
 Prenez note qu'avec le bloc try, différentes méthodes peuvent générer la même exception mais vous n'aurez besoin que d'un seul gestionnaire d'exception.
 

Terminaison contre Restauration


 Ce sont les deux modèles de base dans la théorie de la gestion des exceptions. Dans la terminaison (supportée par Java et C++) on suppose que l'erreur est si critique qu'il n'est pas possible de recommencer l'exécution à partir de l'endroit ou c'est produit l'exception. Celui qui génère l'exception décide qu'il n'y a pas de solution pour restaurer la situation et ne veut pas revenir en arrière.
 L'autre solution est appelée restauration. Cela signifie que le gestionnaire d'exception est censé agir pour corriger la situation, et ainsi la méthode incriminée est de nouveau exécutée avec un succès présumé. Si vous voulez utilisez la restauration, cela signifie que vous voulez que l'exécution continue après le traitement de l'exception. Dans cette optique votre exception est résolue par un appel de méthode qui est la façon dont vous devriez résoudre vos problèmes en Java si vous voulez avoir un comportement de Type restauration dans la gestion de vos exceptions. autrement placez votre bloc try dans un bloc while qui continuera a exécuter le bloc try tant que le résultat ne sera pas satisfaisant.
 Historiquement, les programmeur utilisant des systèmes d'exploitation qui supportaient la gestion des exceptions restauratrice finissaient par utiliser un code qui ressemblait à celui de la terminaison laissant de côté la restauration. Bien que la restauration ait l'air attractive au départ elle n'est pas aisée à mettre en oeuvre. La raison dominante est le couplage que cela générait : votre gestionnaire d'exception doit être conscient de l'endroit ou est générée l'exception et contenir du code générique indépendant de la localisation de sa génération. Cela rend le code difficile à écrire et à maintenir, surtout dans le cadre de grands systèmes où les exceptions peuvent surgir de nulle part.
 

Créez vos propres Exceptions


 Vous n'êtes pas obligés de vous cantonner aux exceptions Java. Ceci est important car vous aurez souvent besoin de créez vos exceptions pour distinguer une erreur que votre librairie est capable de générer, mais qui n'a pas été prévue lorsque la hiérarchie des exceptions Java a été pensée.
 Pour créer votre propre classe d'exception, vous devez hériter d'un type d'exception déjà existant, de préférence une qui est proche de votre nouvelle exception ( parfois ce n'est pas possible). La façon la plus simple de créer une exception est de laisser le compilateur créer le constructeur par défaut, ainsi vous n'avez pas besoin d'écrire de code.
 
 //: c10:SimpleExceptionDemo.java
// Inheriting your own exceptions.
class SimpleException  extends Exception {}
public  class SimpleExceptionDemo {
  public  void f()  throws SimpleException {
    System.out.println(
      "Throwing SimpleException from f()");
    throw  new SimpleException ();
  }
  public  static  void main(String[] args) {
    SimpleExceptionDemo sed =
      new SimpleExceptionDemo();
    try {
      sed.f();
    } catch(SimpleException e) {
      System.err.println("Caught it!");
    }
  }
} ///:~

 Quand le compilateur crée le constructeur par défaut, celui ci automatiquement appelle le constructeur de base par défaut de la classe. Bien sûr vous n'obtenez pas un constructeur SimpleException(String) par défaut mais ce constructeur n'est guère utilisé. Comme vous le constaterez la chose la plus importante à propos d'une exception est le nom de sa classe, donc la plupart du temps une exception comme celle décrite au dessus est satisfaisante.
 Ici le résultat est envoyé vers le flux d'.erreur standard de la console en écrivant dans System.err. C'est habituellement un meilleur endroit pour diriger les informations sur les erreurs que System.Out, qui peut être re-dirigé. Si vous envoyez le flux de sortie vers System.err il ne sera pas re-dirigé de la même façon que System.out donc l'utilisateur pourra le remarquer plus aisément.
 Créer une classe d'exception dont le constructeur a aussi un constructeur qui prend un argument de type string est assez simple :
 
//: c10:FullConstructors.java
// Inheriting your own exceptions.
class MyException  extends Exception {
  public MyException() {}
  public MyException(String msg) {
    super(msg);
  }
}
public  class FullConstructors {
  public  static void f()  throws MyException {
    System.out.println(
      "Throwing MyException from f()");
    throw  new MyException();
  }
  public  static void g()  throws MyException {
    System.out.println(
      "Throwing MyException from g()");
    throw  new MyException( "Originated in g()");
  }
  public  static  void main(String[] args) {
    try {
      f();
    } catch(MyException e) {
      e.printStackTrace(System.err);
    }
    try {
      g();
    } catch(MyException e) {
      e.printStackTrace(System.err);
    }
  }
} ///:~

 Le code ajouté est minime, deux constructeurs qui définissent la façon dont MyException est crée. Dans le second constructeur, le constructeur de base avec un argument de type String est appelé de façon explicité avec le mot clé super.
 L'information de la pile des appels est redirigée vers System.err ainsi on notera plus simplement dans l'événement que system.out a été redirigé.
 La sortie du programme est :
 
Throwing MyException from f()
MyException
        at FullConstructors.f(FullConstructors.java:16)
        at FullConstructors.main(FullConstructors.java:24)
Throwing MyException from g()
MyException: Originated in g()
        at FullConstructors.g(FullConstructors.java:20)
        at FullConstructors.main(FullConstructors.java:29)

 Vous Pouvez noter l'absence de message de détail dans le MyException généré depuis f().
 La création d'exception personnalisées peut être plus poussée. Vous pouvez ajouter des constructeurs et des membres supplémentaires.
 
//: c10:ExtraFeatures.java
// Further embellishment of exception classes.
class MyException2  extends Exception {
  public MyException2() {}
  public MyException2(String msg) {
    super(msg);
  }
  public MyException2(String msg,  int x) {
    super(msg);
    i = x;
  }
  public  int val() { return i; }
  private int i;
}
public  class ExtraFeatures {
  public  static void f()  throws MyException2 {
    System.out.println(
      "Throwing MyException2 from f()");
    throw  new MyException2();
  }
  public  static void g()  throws MyException2 {
    System.out.println(
      "Throwing MyException2 from g()");
    throw  new MyException2( "Originated in g()");
  }
  public  static void h()  throws MyException2 {
    System.out.println(
      "Throwing MyException2 from h()");
    throw  new MyException2(
      "Originated in h()", 47);
  }
  public  static  void main(String[] args) {
    try {
      f();
    } catch(MyException2 e) {
      e.printStackTrace(System.err);
    }
    try {
      g();
    } catch(MyException2 e) {
      e.printStackTrace(System.err);
    }
    try {
      h();
    } catch(MyException2 e) {
      e.printStackTrace(System.err);
      System.err.println( "e.val() = " + e.val());
    }
  }
} ///:~

 Un membre i a été ajouté, avec une méthode qui lit cette donnée et un constructeur supplémentaire qui l'initialise. Voici la sortie :
 
Throwing MyException2 from f()
MyException2
        at ExtraFeatures.f(ExtraFeatures.java:22)
        at ExtraFeatures.main(ExtraFeatures.java:34)
Throwing MyException2 from g()
MyException2: Originated in g()
        at ExtraFeatures.g(ExtraFeatures.java:26)
        at ExtraFeatures.main(ExtraFeatures.java:39)
Throwing MyException2 from h()
MyException2: Originated in h()
        at ExtraFeatures.h(ExtraFeatures.java:30)
        at ExtraFeatures.main(ExtraFeatures.java:44)
e.val() = 47

 Puisqu'une exception est juste une sorte d'objet vous pouvez continuer à enrichir vos classes d'exceptions mais gardez à l'esprit que toutes ces améliorations peuvent êtres ignorées par les clients programmeurs utilisant vos paquettages puisqu'ils vont probablement regarder quelle exception pourra être générée et rien de plus. (C'est de cette façon que sont utilisées le plus souvent les librairies d'exceptions Java).
 

Spécifier des Exceptions


 En java vous êtes obligé de fournir au programmeur qui appelle vos méthodes la liste des exceptions pouvant être générées. C'est correct, en effet le programmeur peut ainsi exactement savoir quel code écrire pour attraper toute exception potentielle. Bien sur si le code source est disponible le programmeur pourra y jeter un Sil et rechercher le mot clé throw, mais souvent une librairie est livrée sans les sources. Pour éviter que Cela soit un problème java fournit une syntaxe (et vous oblige à l'utiliser) qui vous permet d'informer formellement le programmeur client quelle exceptions est générée par cette méthode, pour que le programmeur puisse les gérer. c'est la spécification des exceptions et cela fait partie intégrante de la déclaration de la méthode, elle apparaît après la liste des arguments.
 La spécification des exceptions utilise un nouveau mot clé throws suivi par la liste des type d'exceptions possibles. Vos définitions de méthodes pourront ressembler à ceci :
 
void f()  throws TooBig, TooSmall, DivZero {  //...

 Si vous écrivez
 
void f() {  // ...

 Cela signifie qu'aucune exception en pourra être générée par la méthode. (Excepté les exceptions de type RuntimeException, qui peut être générée n'importe où nous verrons cela plus tard.)
 Vous ne pouvez pas mentir à propos de la spécification des exceptions, si votre méthode génère des exceptions sans les gérer le compilateur le détectera et vous dira que vous devez soit gérer ces exceptions ou indiquer en spécifiant que cette exception peut être générée dans votre méthode. En Forçant la spécification des exceptions de haut en bas ,Java garantit que la correction des exceptions est assurée au moment de la compilation.
 Il y a un endroit ou vous pouvez tricher : vous déclarez générer une exception que vous ne n'allez pas générer. Le compilateur vous prend au pied de la lettre, et force l'utilisateur de la méthode à agir comme si elle pouvait générer l'exception. Ceci à l'effet positif d'être un réceptacle pour cette exception, ainsi vous pourrez générer l'exception sans devoir modifier le code existant. C'. est aussi important pour créer des classes abstraites et des interfaces dont les classes dérivées ou les implémentations puissent générer des exceptions.
 

Attraper n'importe quelle exception


 Il est possible de créer un gestionnaire qui intercepte n'importe quel type d'exception. Ceci est réalisé en interceptant le classe de base d'exception Exception (Il y a d'autre types d'exception de base mais Exception est celle qui est pertinent pour tout les types d'activités de programmation).
 
catch(Exception e) {
  System.err.println("Caught an exception");
}

 Ce code interceptera toute les exceptions donc si vous l'utilisez vous voudrez le placer à la fin de votre liste de gestionnaires pour éviter la préemption sur certains gestionnaires qui pourraient dans l'autre cas le suivre.
 Comme la classe Exception est la base de toute les classes d'exception qui sont utiles au programmeur, vous n'avez pas beaucoup d'informations spécifiques sur l'exception, mais vous pouvez utiliser les méthode venant de son type de base Throwable :
 String getMessage() String getLocalizedMessage() Donne le message détaillé ou un message adapté à cette localisation particulière.
 String toString() Retourne une courte description de l'objet Throwable ,incluant le message détaillé si il y en a un.
 void printStackTrace() void printStackTrace(PrintStream) void printStackTrace(PrintWriter) Imprime l'objet throwable ainsi que la trace de la pile des appels de l'objet Throwable. La pile d'appels vous montre la séquence d'appels de méthodes qui vous ont conduit à l'endroit où l'exception a été levée. La première version imprime les informations vers la sortie standard la seconde et la troisième vers un flux de votre choix (Dans le chapitre 11, vous comprendrez pourquoi il y a deux types de flux).
 Throwable fillInStackTrace() Enregistre des informations liées à cet objet Throwable concernant l'état de la « stack frame ». Utile quand une application relance une exception (nous approfondirons cela plus loin).
 En plus, vous pouvez utiliser des méthodes provenant de la classe mère de Throwable qui est objet (ancêtre de tout type de base). Celle qui pourrait être utile pour les exceptions est getClass() qui retourne un objet représentant la classe de cet objet. Vous pouvez interroger cet objet de Class avec les méthodes getName() or toString(). Vous pouvez aussi faire des choses plus sophistiquées avec l'objet Class qui ne sont nécessaires dans la gestion des exceptions. Les objets Class seront étudiés plus loin dans ce livre.
 Voici un exemple qui montre un emploi simple de la méthode d'Exception de base :
 
//: c10:ExceptionMethods.java
// Demonstrating the Exception Methods.
public  class ExceptionMethods {
  public  static  void main(String[] args) {
    try {
      throw  new Exception( "Here's my Exception");
    } catch(Exception e) {
      System.err.println("Caught Exception");
      System.err.println(
        "e.getMessage(): " + e.getMessage());
      System.err.println(
        "e.getLocalizedMessage(): " +
         e.getLocalizedMessage());
      System.err.println("e.toString(): " + e);
      System.err.println( "e.printStackTrace():");
      e.printStackTrace(System.err);
    }
  }
} ///:~

 La sortie du programme est :
 
Caught Exception
e.getMessage(): Here's my Exception
e.getLocalizedMessage(): Here's my Exception
e.toString(): java.lang.Exception:
   Here's my Exception
e.printStackTrace():
java.lang.Exception: Here's my Exception
at ExceptionMethods.main(ExceptionMethods.java:7)
java.lang.Exception:
   Here's my Exception
at ExceptionMethods.main(ExceptionMethods.java:7)

 Vous pouvez observer que les méthodes donnent successivement plus d'informations, chacune est effectivement une évolution de la précédente.
 

Relancer une exception


 Quelques fois vous voudrez relancer une exception que vous venez d'intercepter, particulièrement quand vous utilisez la classe Exception pour attraper n'importe quelle exception. Comme vous avez déjà la référence de l'exception courante vous pouvez tout simplement la re-lancer. >
 
catch(Exception e) {
  System.err.println( "An exception was thrown");
  throw e;
}

 La redirection de l'exception envoie l'exception au gestionnaire d'exception de contexte supérieur le plus proche. Tout autre clause catch placée après dans le même bloc try est ignorée. De plus, toute information concernant l'objet exception est préservée, donc le gestionnaire d'exception du niveau supérieur qui peut attraper ce type d'exception spécifique peut extraire toute les informations de cet objet.
 Si vous redirigez simplement l'exception courante, l'information que vous restituerez fera référence aux origines de l'exception et non de l'endroit ou vous la redirigez. Si vous voulez placez de nouvelles informations dans la pile des appels, vous pouvez le faire en appelant la méthode fillInStackTrace(), qui retourne un objet exception qu'elle a crée en ajoutant les informations actuelles de la pile dans l'ancien objet exception. Voyons à quoi cela ressemble.
 
//: c10:Rethrowing.java
// Demonstrating fillInStackTrace()
public  class Rethrowing {
  public  static void f()  throws Exception {
    System.out.println(
      "originating the exception in f()");
    throw  new Exception( "thrown from f()");
  }
  public  static void g()  throws Throwable {
    try {
      f();
    } catch(Exception e) {
      System.err.println(
        "Inside g(), e.printStackTrace()");
      e.printStackTrace(System.err);
      throw e;  // 17
      // throw e.fillInStackTrace(); // 18
    }
  }
  public  static void
  main(String[] args) throws Throwable {
    try {
      g();
    } catch(Exception e) {
      System.err.println(
        "Caught in main, e.printStackTrace()");
      e.printStackTrace(System.err);
    }
  }
} ///:~

 Les numéros des lignes importantes sont marquées dans les commentaires, avec la ligne 17 non mise en commentaire (cf. le code ci-dessus) l'exécution produit comme sortie :
 
originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:8)
        at Rethrowing.g(Rethrowing.java:12)
        at Rethrowing.main(Rethrowing.java:24)
Caught in main, e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:8)
        at Rethrowing.g(Rethrowing.java:12)
        at Rethrowing.main(Rethrowing.java:24)

 Ainsi la trace de la pile d'exception se souvient toujours de sont vrai point d'origine, sans tenir compte du nombre de fois qu'il sera relancé.
 Avec la ligne 17 mise en commentaire et la ligne 18 décommentée fillInStackTrace() est utilisée ce qui donne le résultat suivant :
 
originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:8)
        at Rethrowing.g(Rethrowing.java:12)
        at Rethrowing.main(Rethrowing.java:24)
Caught in main, e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.g(Rethrowing.java:18)
        at Rethrowing.main(Rethrowing.java:24)

 Grâce à fillInStackTrace() la ligne 18 devient l'origine de l'exception.
 La classe Throwable doit apparaître dans la spécification d'exception pour g() et main() parce que fillInStackTrace() produit une référence à un objet Throwable. Comme Throwable est la classe de base de toute Exception il est possible d'avoir un objet de type Throwable qui ne soit pas une Exception et donc que le gestionnaire d'exception du main() ne le traite pas. Pour être sur que tout soit correct le compilateur force une spécification d'exception pour Throwable. Par exemple l'exception dans le programme suivant n'est pas interceptée dans main() :
 
//: c10:ThrowOut.java
public  class ThrowOut {
  public  static void
  main(String[] args) throws Throwable {
    try {
      throw  new Throwable();
    } catch(Exception e) {
      System.err.println("Caught in main()");
    }
  }
} ///:~

 Il est aussi possible de rediriger une exception différente de celle que vous avez intercepté. En procédant ainsi vous obtenez une effet similaire a l'utilisation de fillInStackTrace() . l'information sur l'emplacement originel de l'exception est perdu, et l'information qu'il vous reste est celle relative au nouveau throw() :
 
//: c10:RethrowNew.java
// Rethrow a different object
// from the one that was caught.
class OneException  extends Exception {
  public OneException(String s) {  super(s); }
}
class TwoException  extends Exception {
  public TwoException(String s) {  super(s); }
}
public  class RethrowNew {
  public  static void f()  throws OneException {
    System.out.println(
      "originating the exception in f()");
    throw  new OneException( "thrown from f()");
  }
  public  static  void main(String[] args)
  throws TwoException {
    try {
      f();
    } catch(OneException e) {
      System.err.println(
        "Caught in main, e.printStackTrace()");
      e.printStackTrace(System.err);
      throw  new TwoException( "from main()");
    }
  }
} ///:~

 Le résultat de l'exécution est :
 
originating the exception in f()
Caught in main, e.printStackTrace()
OneException: thrown from f()
        at RethrowNew.f(RethrowNew.java:17)
        at RethrowNew.main(RethrowNew.java:22)
Exception in thread  "main" TwoException: from main()
        at RethrowNew.main(RethrowNew.java:27)

 L'exception finale sait seulement qu'elle vient de main() et non de f().
 Vous n'avez pas à vous soucier de la destruction des exceptions précédentes. Ce sont tout des objets crées avec l'instruction new(), le garbage collector les détruits donc automatiquement.
 

Les exceptions Java standard


 La classe Java Throwable décrit tout ce qui peut être généré comme exception. Il y a deux sortes d'objets Throwable (« Sortes de » = « Héritées de »). Error représente les erreurs systèmes et de compilation dont vous n'avez pas à vous soucier (excepté quelques cas spécifiques). Exception est le type de base qui peut être généré à partir de n'importe quelle méthode de classe de librairie Java standard. Les Exceptions sont donc le type de base qui intéresse le programmeur Java.
 La meilleure façon d'avoir un aperçu des exceptions est de lire la documentation HTML de Java qui est disponible à java.sun.com. Il est utile de le faire juste pour s'apercevoir de la variété des exception, mais vous verrez rapidement que rien à part le nom ne distingue une exception d'une autre. Comme le nombre d'exceptions Java continue de s'accroître il est inutile d'en imprimer la liste. Chaque nouvelle librairie que vous achèterez à un éditeur aura probablement ses propres exceptions. Le point important à comprendre est le concept et ce que vous devez faire avec les exceptions.
 Le principe de base est que le nom de l'exception représente le problème qui est apparu, et le nom de l'exception est censé être relativement explicite. Toutes les exceptions ne sont pas toutes définies dans java.lang ; certaines ont été crées pour être utilisées dans d'autres libraires telles que util, net et io ce que l'on peut voir à partir du nom complet des classes dont elles ont héritées. Ainsi toutes les exception I/O (Entrée/Sortie) héritent de java.io.IOException.
 

Le cas particulier RuntimeException


 Le premier exemple du chapitre était :
 
if(t ==  null)
  throw  new NullPointerException();



Ce livre a été écrit par Bruce Eckel ( télécharger la version anglaise : Thinking in java )
Ce chapitre a été traduit par Armel Fortun ( groupe de traduction )
télécharger la version francaise (PDF) | Commandez le livre en version anglaise (amazon) | télécharger la version anglaise
pages : 1 2 3
Penser en Java  -  Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières  -  Thinking in Java