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 où
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();