Chapitre 10 - Gestion des erreurs avec les
exceptions
Cela peut paraître effrayant de penser que l'on devra
vérifier le null pour chaque
référence passée à une
méthode(puisque on ne sait pas si l'appelant à
passé une référence valide).
Heureusement vous n'avez pas à le faire. C'est une des
taches standard de run-time checking que Java exécute pour
vous, et si un appel est fait à une
référence contenant la valeur null,
Java va automatiquement générer une NullPointerException.
Donc le code ci-dessus est superflu.
Il y a tout un groupe d'exceptions
qui sont dans cette
catégorie. Elles sont
générées automatiquement par Java et
vous n'avez pas besoin de les inclure dans vos
spécifications d'exception. Elles sont regroupées
dans une seule classe de base nommée RuntimeExceptions, qui
est un parfait exemple de l'héritage : cela
établit une famille de types qui ont des
caractéristiques et des comportements communs. Aussi vous
n'avez jamais besoin de spécifier qu'une méthode
peut générer une RuntimeException puisque c'est
géré. Puisque cela indique la présence
de bogues vous n'interceptez jamais de RuntimeException
c'est géré automatiquement. Si vous
étiez obligés de tester les RuntimeException
votre code pourrait devenir illisible. Bien que vous n'interceptiez pas
de RuntimeException dans vos propre paquettages vous pourriez
décider de générer des
RuntimeException.
Que se passe t'il quand vous
n'interceptez pas de telles exceptions ?
Comme le compilateur ne demande pas de spécifications
d'exceptions pour celles ci il est probable qu'une RuntimeException
puisse remonter jusqu'à votre méthode main()
sans être interceptée. Pour voir ce qui se passe
dans ce cas là essayez l'exemple suivant :
//: c10:NeverCaught.java
// Ignoring RuntimeExceptions.
public class NeverCaught {
static void f() {
throw new RuntimeException( "From f()");
}
static void g() {
f();
}
public static void main(String[] args) {
g();
}
} ///:~
vous pouvez
déjà voir qu'une RuntimeException
(ou tout ce qui en hérite) est un cas particulier, puisque
le compilateur ne demande pas de spécifications pour ce
type.
La trace est :
Exception in thread "main"
java.lang.RuntimeException: From f()
at
NeverCaught.f(NeverCaught.java:9)
at
NeverCaught.g(NeverCaught.java:12)
at
NeverCaught.main(NeverCaught.java:15)
La réponse est donc : Si
une RuntimeException
arrive jusqu'à la méthode main()
sans être interceptée, PrintStackTrace()
est appelée pour cette exception à la sortie du
programme.
Gardez à l'esprit que
vous pouvez ignorer seulement les RuntimeException
dans vos développements puisque la gestion de toutes les
autres exceptions est renforcée par le compilateur. Le
raisonnement est qu'une RuntimeRxception
représente une erreur de programmation.
- Une erreur que vous ne pouvez pas intercepter(
recevoir une référence transmise par un programme
client avec la valeur null par exemple).
- Une erreur que vous auriez du prévoir
dans votre code (comme ArrayIndexOutOfBoundsException
ou vous auriez du faire attention à la taille du tableau).
Vous pouvez voir quel
bénéfice apportent les
exceptions dans ce cas puisque cela aide le processus de
déboguage.
Il est intéressant de
noter que vous ne pouvez pas ranger le
gestion des exceptions Java dans un outil à utilisation
unique. Oui c'est fait pour gérer c'est affreuses run-time
error qui vont apparaître à causes de forces hors
de contrôle de votre code mais c'est aussi essentiel pour
certains types de bogues de développement que le compilateur
ne peut détecter.
Faire le ménage avec finally
Il y a souvent un morceau de code
que vous voulez exécuter
qu'une exception ou pas soit générée
au coeur d'un bloc try. C'est
généralement destiné à des
opérations autre que le rafraîchissement de la
mémoire (puisque le garbage collector s'en occupe). Pour
faire ceci il faut utiliser finally à la
fin de chaque gestionnaire d'exceptions. L'image complète
d'un paragraphe gestion d'exception est :
try {
// The guarded region:
Dangerous activities
// that might throw A,
B, or C
} catch(A a1) {
// Handler for
situation A
} catch(B b1) {
// Handler for
situation B
} catch(C c1) {
// Handler for
situation C
} finally {
// Activities that
happen every time
}
Pour démontrer que la
clause finally est
toujours exécutée lancer ce programme :
//: c10:FinallyWorks.java
// The finally clause is always
executed.
class ThreeException extends Exception {}
public class FinallyWorks {
static int count = 0;
public static void main(String[] args) {
while(true) {
try {
// Post-increment is zero first time:
if(count++ == 0)
throw new
ThreeException();
System.out.println("No exception");
} catch(ThreeException e) {
System.err.println("ThreeException");
} finally {
System.err.println( "In
finally clause");
if(count == 2) break; // out of "while"
}
}
}
} ///:~
Ce programme vous donne aussi une
aide pour comprendre comment
gérer le fait que Java(comme les exceptions en C++) ne vous
permet de reprendre l'exécution de l'endroit où
l'exception a été
générée, comme vu plus tôt.
Si vous placez votre bloc try dans une boucle, vous
pouvez établir un condition qui doit être
validée avant de continuer le programme. Vous pouvez aussi
ajouter un compteur de type static ou d'autres
périphériques afin de permettre à la
boucle différente approches avant d'abandonner. De cette
façon vous pouvez obtenir un plus grand niveau de robustesse
dans vos programmes.
La sortie est :
ThreeException
In finally clause
No exception
In finally clause
Qu'une exception soit
générée ou pas
la clause finally est
exécutée.
À Quoi sert le finally ?
Dans un langage sans ramasse-miettes
[Garbage Collector] et
sans appel automatique aux destructeurs, finally
est important parce qu'il permet au programmeur de garantir la
libération de la mémoire
indépendamment de ce qui se passe dans le bloc try.
Mais Java a un ramasse-miettes. Donc il n'a aucun destructeurs
à appeler. Donc quand avez vous besoin de la clause finally
en Java ?
Finally est
nécessaire quand vous
avez besoin de remettre à l'état original autre
chose que le mémoire. C'est une sorte de nettoyage comme un
fichier ouvert une connexion réseau quelque chose
affiché à l'écran ou un switch dans le
monde extérieur comme modélisé dans
l'exemple suivant :
//: c10:OnOffSwitch.java
// Why use finally?
class Switch {
boolean
state = false;
boolean
read() { return state; }
void on()
{ state = true; }
void
off() { state = false; }
}
class OnOffException1 extends Exception {}
class OnOffException2 extends Exception {}
public class OnOffSwitch {
static
Switch sw = new Switch();
static void f() throws
OnOffException1,
OnOffException2 {}
public static void main(String[] args) {
try
{
sw.on();
// Code that can throw exceptions...
f();
sw.off();
} catch(OnOffException1
e) {
System.err.println("OnOffException1");
sw.off();
} catch(OnOffException2
e) {
System.err.println("OnOffException2");
sw.off();
}
}
} ///:~
L'objectif ici est d'être
sûr que le switch est Off
quand la méthode main() se termine donc sw.off()
est placée à la fin de main()
et à la fin de chaque gestionnaire d'exception mais il se
peut qu'une exception soit générée et
qu'elle ne soit pas interceptée ici. Vous pouvez placer le
code de nettoyage dans un seul endroit le bloc finally
:
//: c10:WithFinally.java
// Finally Guarantees cleanup.
public class WithFinally {
static
Switch sw = new Switch();
public static void main(String[] args) {
try
{
sw.on();
// Code that can throw exceptions...
OnOffSwitch.f();
} catch(OnOffException1
e) {
System.err.println("OnOffException1");
} catch(OnOffException2
e) {
System.err.println("OnOffException2");
} finally
{
sw.off();
}
}
} ///:~
Ici le sw.off()
à
été placée à un endroit
unique ou on est sûr qu'il sera
exécutée quoi qu'il arrive.
Même dans le cas
où l'exception n'est pas
interceptée dans l'ensemble des clauses catch,
finally sera exécuté
avant que le mécanisme de gestion d'exception recherche un
gestionnaire de plus haut niveau.
//: c10:AlwaysFinally.java
// Finally is always executed.
class FourException extends Exception {}
public class AlwaysFinally {
public static void main(String[] args) {
System.out.println(
"Entering first try block");
try
{
System.out.println(
"Entering second try block");
try {
throw new
FourException();
} finally {
System.out.println(
"finally in 2nd try block");
}
} catch(FourException
e) {
System.err.println(
"Caught
FourException in 1st try block");
} finally
{
System.err.println(
"finally in 1st try block");
}
}
} ///:~
La sortie de ce programme vous
montre ce qui arrive :
Entering first try block
Entering second try block
finally in 2nd try block
Caught FourException in 1st try
block
finally in 1st try block
Le mot clé finally
sera aussi
exécuté dans des situations ou les mots
clés break et continue
sont impliqués. Notez qu'avec break continue et finally il
n'y a pas besoin de goto en Java.
Le défaut : l'exception perdue
En général
l'implémentation de Java
est de grande qualité mais malheureusement il y a un petit
défaut. Bien qu'une exception soit une indication de crise
dans votre programme et ne doit jamais être
ignorée il est possible qu'une exception soit perdue. Cela
est possible dans une certaine configuration de la clause finally
:
//: c10:LostMessage.java
// How an exception can be lost.
class
VeryImportantException extends Exception {
public
String toString() {
return "A very important exception!";
}
}
class HoHumException extends Exception {
public
String toString() {
return "A trivial exception";
}
}
public class LostMessage {
void f() throws VeryImportantException {
throw new VeryImportantException();
}
void
dispose() throws HoHumException {
throw new HoHumException();
}
public static void main(String[] args)
throws Exception {
LostMessage lm = new LostMessage();
try
{
lm.f();
} finally
{
lm.dispose();
}
}
} ///:~
Le résultat est :
Exception in thread "main" A trivial exception
at
LostMessage.dispose(LostMessage.java:21)
at
LostMessage.main(LostMessage.java:29)
Vous pouvez voir qu'il ne reste
aucune trace de VeryImportantException
qui est tout simplement remplacée par HoHumException
dans la clause finally. Ceci est un faille
sérieuse puisqu'il signifie qu'une exception peut
être complètement perdue et bien plus difficile et
subtile à détecter que les autres. C++ lui au
contraire traite la situation en considérant qu'une seconde
exception générée avant la
première est une erreur de programmation. Peut
être qu' une future version de Java corrigera ce
défaut (autrement vous pouvez exclure du bloc try-catch
toute méthode susceptible de générer
une exception telle que dispose()).
Restriction d'Exceptions
Quand vous surchargez une
méthode vous ne pouvez
générer que les exceptions qui ont
été spécifiées dans la
classe de base de la version de la méthode. c'est une
restriction très utile puisqu'elle garantit que le code qui
fonctionne avec la version de classe de base de la méthode
fonctionnera avec tout les objets dérivant de cette classe (
un concept fondamental de la POO),incluant les exceptions.
Cet exemple démontre les
restrictions imposée
(à la compilation) pour les exceptions :
//: c10:StormyInning.java
// Overridden methods may throw only
the
// exceptions specified in their
base-class
// versions, or exceptions derived
from the
// base-class exceptions.
class BaseballException extends Exception {}
class Foul extends BaseballException {}
class Strike extends BaseballException {}
abstract class Inning {
Inning() throws
BaseballException {}
void
event () throws BaseballException {
// Doesn't actually
have to throw anything
}
abstract void atBat() throws Strike, Foul;
void
walk() {} // Throws nothing
}
class StormException extends Exception {}
class RainedOut extends StormException {}
class PopFoul extends Foul {}
interface Storm {
void
event() throws RainedOut;
void
rainHard() throws RainedOut;
}
public class StormyInning extends Inning
implements
Storm {
// OK to add new
exceptions for
// constructors, but
you must deal
// with the base
constructor exceptions:
StormyInning() throws
RainedOut,
BaseballException {}
StormyInning(String s) throws
Foul,
BaseballException {}
// Regular methods must conform to
base class:
//! void walk() throws PopFoul {} //Compile error
// Interface CANNOT add exceptions
to existing
// methods from the
base class:
//! public void event() throws
RainedOut {}
// If the method
doesn't already exist in the
// base class, the
exception is OK:
public void rainHard() throws RainedOut {}
// You can choose to not throw any
exceptions,
// even if base
version does:
public void event() {}
// Overridden methods
can throw
// inherited
exceptions:
void
atBat() throws PopFoul {}
public static void main(String[] args) {
try
{
StormyInning
si = new StormyInning();
si.atBat();
} catch(PopFoul
e) {
System.err.println("Pop foul");
} catch(RainedOut
e) {
System.err.println("Rained out");
} catch(BaseballException
e) {
System.err.println("Generic error");
}
//
Strike not thrown in derived version.
try
{
// What happens if you upcast?
Inning i = new StormyInning();
i.atBat();
// You must catch the exceptions from the
// base-class version of the method:
} catch(Strike
e) {
System.err.println("Strike");
} catch(Foul
e) {
System.err.println("Foul");
} catch(RainedOut
e) {
System.err.println("Rained out");
} catch(BaseballException
e) {
System.err.println(
"Generic baseball exception");
}
}
} ///:~
Dans Inning()
vous pouvez voir qu'à la
fois le constructeur et la méthode event()
déclarent qu'elles peuvent lancer une exception alors
qu'elles ne le feront jamais. C'est tout à fait
légitime puisqu'elles vous laissent la
possibilité de forcer l'utilisateur à attraper
des exceptions dans des versions surchargées de event().
Le même principe s'applique pour les méthodes abstraites
[abstract].
L'interface Storm
est intéressante parce
qu'elle contient une méthode qui est définie dans
Inning et une autre qui ne l'est pas. Toutes
génèrent un nouveau type d'exception, RainedOut.
Quand vous regardez StormyInning extends Inning implements
Storm. Vous voyez que la méthode event()
de Storm ne peut pas changer
l'interface de la méthode event() de Inning.
Une fois encore cela est sensé car lorsque que vous
travaillez avec la classe de base vous ne sauriez pas si vous attrapez
la bonne chose. Bien sûr si un méthode
décrite dans l'interface n'est pas dans la classe de base
telle que Rainhard(), ainsi il n'y a aucun
problème lorsqu'elle génèrent des
exceptions.
La restriction sur les exceptions ne
s'appliquent pas aux
constructeurs. Vous pouvez voir dans Stormyinning
un constructeur qui peut générer toutes les
exceptions qu'il veut sans être contraint par le constructeur
de base. Malgré tout puis un constructeur de classe de base
doit bien être appelé à un moment ou
à un autre (ici le constructeur par défaut est
appelé automatiquement) le constructeur de la classe
dérivée doit déclarer toute les
exceptions du constructeur de base dans ses spécifications
d'exception. un constructeur de classe dérivée ne
peut pas intercepter les exceptions de sa classe de base.
La raison pour laquelle StormyInning.Walk()
ne
compilera pas est qu'elle génère une exception
alors que Inning.walk() n'en génère pas. Si cela
était permis vous pourriez écrire le code qui
appelle Inning.walk() et qui n'aurait pas
à gérer une seule exception mais lorsque vous
substitueriez à Inning un objet d'un de
ses classes dérivées des exceptions seraient
levées et votre code serait interrompu. En
forçant les méthodes de classes
dérivées à se conformer aux
spécifications d'exceptions des classes de base la
substituabilité des objets est maintenue.
La méthode
surchargée event()
montre qu'un méthode héritée d'une
classe peut choisir de ne pas générer
d'exceptions même si la méthode de la classe de
base le fait. Ceci est de nouveau bon puisque cela ne rend caduque
aucun code qui a été écrit
présumant que la classe de base génère
des exceptions. La même logique s'applique à atBat()
qui génère une exception PopFoul,
une exception qui est dérivée de l'exception Foul
par la version de base de atBat(). De cette
façon si quelqu'un écrit du code qui fonctionne
avec Inning() et qui appelle atBat()
doit intercepter les exception Foul. Puisque PopFoul
hérite de Foul le gestionnaire
d'exception interceptera PopFoul.
Le dernier point
intéressant est main()
Ici vous pouvez voir que si vous avez à faire à
un objet StormyInning le compilateur vous oblige à
intercepter que les exceptions qui sont spécifiques
à la classe mais si vous caster l'objet le compilateur vous
oblige (avec raison) à gérer toutes les
exceptions du type de base. Toute ces contraintes produisent du code de
gestion des exceptions beaucoup plus robustes.
Il est utile de réaliser
que bien la
spécifications des exceptions soit renforcée au
moment de l'héritage par le compilateur, la
spécification des exceptions n'est pas une partie d'un type
d'une méthode, qui est composée seulement d'un
nom de méthode et d'un type d'arguments. vous ne pouvez pas
surcharger une méthode basée sur une
spécification d'exception. De plus il n'est pas obligatoire
qu'une exception spécifiée dans
méthode d'une classe de base soit présente dans
la méthode d'une classe en héritant. C'est assez
différent des règles de l'héritage,
où un méthode qui existe dans une classe de base
doit aussi exister dans sa fille. Autrement dit l'interface de
spécification des exceptions d'une classe ne peut que se
restreindre alors que c'est le contraire pour l'interface de la classe
durant l'héritage.
Les constructeurs
Quand on écrit du codes
avec des spécifications
d'exceptions il y a une question particulière que l'on doit
se poser « si une exception se produit est ce que la
situation sera rétablie à l'état
initial ? » dans la plupart des cas vous êtes
sauvés mais dans celui des constructeurs il y a un
problème. Le constructeur place l'objet dans un
état de démarrage sain mais peut accomplir
certaines opérations, telles que ouvrir un fichier- qui ne
seront pas terminées tant que l'utilisateur n'aura pas
terminé de travailler avec l'objet et appelé une
certaine méthode de restauration. Si vous
générez une exception dans le constructeur ces
restaurations peuvent ne pas s'effectuer de façon
appropriée. Cela signifie que vous devez être
extrêmement prudent quand vous écrivez votre
propre constructeur.
Comme vous avez venez d'apprendre le
mot clé finally
vous pouvez penser que c'est la solution mais ce n'est pas si simple,
parce que Finally fait le nettoyage du code à chaque
exécution même dans les cas ou vous ne le
souhaiteriez pas. Aussi si vous faites la restauration dans le finally
vous devez placer un indicateur qui vous indiquera de ne rien faire
dans le finally si le constructeur c'est bien
déroulé mais cela n'est pas
particulièrement élégant (vous liez
votre code d'un endroit à un autre) c'est mieux de ne pas
faire ce genre d'opérations dans le finally
à moins d'y être forcé.
Dans l'exemple suivant une classe
appelée InputFile
est crée elle vous permet d'ouvrir un fichier et d'en lire
une ligne (convertie en String) à la
fois . Elle utilise les classes FileReader et InputBuffer
de la librairie I/O standard de Java dont nous discuterons au chapitre
11 mais elles sont assez simple donc vous n'aurez probablement pas de
problème à comprendre leur comportement de base :
//: c10:Cleanup.java
// Paying attention to exceptions
// in constructors.
import java.io.*;
class InputFile {
private
BufferedReader in;
InputFile(String fname) throws
Exception {
try
{
in =
new BufferedReader(
new FileReader(fname));
// Other code that might throw exceptions
} catch(FileNotFoundException
e) {
System.err.println(
"Could not open " + fname);
// Wasn't open, so don't close it
throw e;
} catch(Exception
e) {
// All other exceptions must close it
try {
in.close();
} catch(IOException e2) {
System.err.println(
"in.close() unsuccessful");
}
throw e; // Rethrow
} finally
{
// Don't close it here!!!
}
}
String getLine() {
String s;
try
{
s =
in.readLine();
} catch(IOException
e) {
System.err.println(
"readLine() unsuccessful");
s = "failed";
}
return
s;
}
void
cleanup() {
try
{
in.close();
} catch(IOException
e2) {
System.err.println(
"in.close() unsuccessful");
}
}
}
public class Cleanup {
public static void main(String[] args) {
try
{
InputFile
in =
new
InputFile( "Cleanup.java");
String s;
int i = 1;
while((s = in.getLine()) != null)
System.out.println(""+ i++ + color= ": "
+ s);
in.cleanup();
} catch(Exception
e) {
System.err.println(
"Caught in main, e.printStackTrace()");
e.printStackTrace(System.err);
}
}
} ///:~
Le constructeur d'InputFile
prend une String
comme paramètre, qui contient le nom du fichier que vous
voulez ouvrir, à l'intérieur du bloc try
il crée un FileReader. Un FileReader
n'est pas particulièrement utile à moins que vous
ne le détourniez et l'utilisiez pour créer un BuffereReader
avec lequel vous puissiez communiquer, un des
bénéfices de l'InputFile est
qu'il combine ses deux actions.
Si le constructeur de FileReader
échoue
il génère une FileNotFoundException
qui doit être interceptée
séparément puisque dans ce cas la vous ne voulez
pas fermer le fichier qui n'a pas été ouvert.
Toute autre clause catch doit fermer le fichier
puisqu'il a été ouvert (bien
sûr c'est plus délicat plus d'une
méthode peut générer une FileNotfoundException
dans ce cas vous pouvez vouloir séparer cela dans plusieurs
blocs try). La méthode close()
doit lancer une exception ce qui est donc essayé et
capté bien que cela soit dans le bloc d'une autre clause catch
- c'est juste une autre paire d'accolades pour le compilateur Java.
Après l'exécution des opérations
locales, l'exception est relancée, ce qui est
adéquat puisque le constructeur a
échoué, et vous ne voudriez pas que la
méthode appelé ai a assumer que l'objet ai
été correctement crée et soit valide.
Dans cet exemple, qui n'utilise pas
la technique du drapeau
mentionnée précédemment, la cause finally
n'est définitivement pas la place pour close()
(ndt. fermer) le fichier, puisque cela fermerait le fichier
à chaque finalisation du constructeur. Comme nous
désirons que le fichier soit ouvert tout le temps de la
durée de vie de l'objet InputFile cela
ne serait pas approprié.
La méthode getLine()
renvoie un String
contenant la prochaine ligne du fichier. Elle appelle readLine(),
qui peut lancer une exception, mais cette exception est
provoqué donc getLine() ne lance pas
d'exceptions. Une des finalités de la conception des
exceptions est soit de porter complètement une exception
à ce niveau, de la porter partiellement et de passer la
même exception (ou une différente), ou soit de la
passer tout simplement. La passer, quand c'est approprié,
peut certainement simplifier le codage. La méthode getLine()
devient :
String getLine() throws IOException {
return
in.readLine();
}
Mais bien sûr, l'appelant
a maintenant la
responsabilité de porter toute IOException
qui puisse apparaître.
La méthode cleanUp()
doit être
appelée par l'utilisateur lorsqu'il a fini d'utiliser
l'objet InputFile(). Ceci relâchera les
ressources système (comme les poignées de file)
qui étaient utilisées par les objets BufferedReader
ou/et FileReader. Vous ne voudrez pas faire cela
tant que vous n'aurez pas fini avec l'objet InputFile, au point de le
laisser partir. Vous devrez penser a mettre une
fonctionnalité de ce type dans une méthode finaliste(),
mais comme il est mentionné dans le Chapitre 4 vous ne
pouvez pas toujours être sûr que finaliste() sera
appelé (même si vous pouvez
être sûr de son appel, vous ne savez pas quand).
C'est un des à-cotés de Java : tout
nettoyage - autre que le nettoyage de la mémoire - ne se
lance pas automatiquement, donc vous devez informer le programmeur
client qu'il est responsable, et garantir au mieux que possible que le
nettoyage s'exécute par l'usage de finalise().
Dans Cleanup.java
un InputFile
est crée pour ouvrir le même fichier source qui
crée le programme, le fichier est lu une ligne à
la fois, et les numéros de ligne sont ajoutés.
Toutes les exceptions sont causées
génériquement dans main(), si
bien que vous pouvez choisir une plus grande finesse.
Un des
bénéfices de cet exemple est de vous
montrer pourquoi les exceptions sont abordés à ce
point du livre - vous ne pouvez pas faire de l'E/S [I/O] sans utiliser
les exceptions. Les exceptions sont tellement
intégrés à la programmation en Java,
spécialement parce que le compilateur les demande, que vous
pouvez accomplir tellement peu de choses sans savoir comment travailler
avec elles.
Indication d'Exception
Quand une exception est
lancée, le système
d'indication des exceptions regarde dans les « plus
proches » identifiants dans l'ordre de leur
écriture. Quand il trouve une correspondance, l'exception
est considérée comme identifiée, et
aucune autre recherche n'est lancée.
Identifier une exception ne requiert
pas de correspondance exacte entre
l'exception et sont identifiant. Une objet de classe
dérivée pourra correspondre à un
identifiant de la classe de base, comme montré dans cet
exemple : "Index1125">
//: c10:Human.java
// Catching exception hierarchies.
class Annoyance extends Exception {}
class Sneeze extends Annoyance {}
public class Human {
public static void main(String[] args) {
try
{
throw new Sneeze();
} catch(Sneeze
s) {
System.err.println("Caught Sneeze");
} catch(Annoyance
a) {
System.err.println("Caught Annoyance");
}
}
} ///:~
L'exception Sneeze
sera
piégée par le premier catch
(ndt. cause) qui correspondra - qui sera le premier, bien
sûr. Cependant, si vous retirez la première cause
du piège, laissant seulement :
try {
throw new Sneeze();
} catch(Annoyance
a) {
System.err.println("Caught Annoyance");
}
Le code sera toujours fonctionnel
puisqu'il piège la classe
de base de Sneeze. Placé d'une autre
manière, catch(Annoyancee) identifiera
une Annoyance ou tout autre classe qui en
est dérivée. C'est utile puisque si
vous décidez d'ajouter plus d'exceptions
dérivées à une méthode,
alors le code du programmeur client n'aura pas besoin d'être
modifié tant que le client piégera les exceptions
de la classe de base.
Si vous essayez de
« masquer »
les exceptions de la classe dérivée en
plaçant le piège de classe-de-base en premier,
comme ceci :
try {
throw new Sneeze();
} catch(Annoyance
a) {
System.err.println("Caught Annoyance");
} catch(Sneeze
s) {
System.err.println("Caught Sneeze");
}
le compilateur vous donnera un
message d'erreur, en voyant que la cause
d'identification de Sneeze ne sera jamais atteinte.