Chap. 7 – Compl.
Dernière MAJ: 01.12.2009 / H. Nguyen-Phu
INTRODUCTION A LA GESTION D'EXCEPTIONS
Plan:
------
Les instructions de gestion des exceptions
Interception d'exceptions par 'try' ... 'catch' ...
'finally'
Le mot clé 'throws' pour relayer
une exception
Le mot clé 'throw' pour lever une exception
Exemple d'erreur dérivant de la classe 'java.lang.Error'
Exemple d'exceptions dérivant de la classe 'RuntimeException'
Un exemple d'exception contrôlée: 'FileNotFoundException'
Enrichissement de la hiérarchie
des classes d'exceptions
Annexe: Une arborescence
partielle de la classe ' Throwable
'
------------------------------------------------------------------------------
***************
Sous Java, on distingue deux sortes d'erreurs: les exceptions et les
erreurs (cf. la classe 'java.lang.Error'
et ses dérivées). Ces dernières
entraînent, en général, l'arrêt brutal de l'exécution d'un programme. Quant aux erreurs "légères"
ou exceptions, on peut considérer que ce sont des incidents qui peuvent
interrompre son déroulement "normal".
Au sein des exceptions, il existe en fait aussi deux types d'exceptions:
(i)
les exceptions "non contrôlées" qui dérivent toutes de la
classe 'java.lang.RuntimeException'
peuvent être seulement "interceptées"
ou relayées aux méthodes appelantes via la clause 'throws';
(ii) les autres exceptions, dites les
exceptions "contrôlées" qui peuvent
être interceptées et aussi "traitées" via les clauses 'try' ... 'catch' ... 'finally' ou "levées (ou lancées)" par un
développeur d'application via la clause 'throw'.
Par définition, une exception contrôlée
est une exception qui DOIT être capturée ('catch') ou levée ('throw') par le
programme applicatif. Par la suite, on
ne considère que les exceptions contrôlées de Java.
Une version simplifiée de la hiérarchie
des classes d'erreurs
('java.lang.Error') et d'exceptions ('java.lang.Exception')
qui dérivent toutes les deux de la classe
'Throwable' est donnée par:
Object
------
|
|
java.lang.Throwable
--------------------
|
| java.lang.Error
| ---------------
| java.lang.Exception
--------------------
|
| java.lang.RuntimeException
| --------------------------
|
"lesExceptionsContrôlées" (Ex. java.io.IOException ...)
-------------------------
2. Les instructions de gestion des exceptions
*********************************************
2.1 Interception
d'exceptions par 'ESSAI' ... 'CAPTURE' ... 'ENFIN'
===================================================================
La syntaxe de cette instruction composée permet la gestion directe
d'une ou de plusieurs levées d'exceptions
selon le schéma ci-dessous:
try { // <=>
ESSAI en L.A.O.
// bloc d'instructions susceptible de générer des exceptions ...
}
catch (uneClasseDException uneException_1) { //
<=> CAPTURE(
... )
// bloc correspondant à un type d'erreur No 1 ...
}
[
... ]
catch (uneAutreClasseDException uneException_k) {
// bloc correspondant à un type d'erreur No k ...
}
finally { // <=>
ENFIN
//
séquence d'instructions à exécuter dans tous les cas ...
}
Le fichier 'div_zero.java' donne justement
un exemple d'emploi de ces
mot-clés:
import java.io.*;
class div_zero {
public static PrintWriter jerr = new PrintWriter(System.err, true);
public static void main(String[] arg) {
int zero = 0;
jerr.println("Emploi de 'try...catch...finally' (c)~/2A env. \n");
try {
zero = 2002/zero;
}
catch
(ArithmeticException
ae) {
jerr.println("Une exception arithmétique (non contrôlée) est levée !");
jerr.println("Message :
" + ae.getMessage());
jerr.println("venant de : " + ae.getClass());
jerr.println("\nPile d'exécution : "); ae.printStackTrace();
}
finally
{
jerr.println("Démo. de 'try...catch...finally' terminée !");
}
es.attente(); // pause pour lire ...
}
}
EXERCICE 1:
Re-prendre la résolution de l'équation du premier degré en traitant l'exception
'NumberFormatException'
lors de la saisie des données réelles doubles par des caractères.
Solution typique (cf. fichier 'eq1_err.java')
----------------
import java.io.*;
class eq1_err
{
// A fourth
Java version to compute 'ax+b = 0'
static PrintWriter jout = new PrintWriter(System.out, true);
static PrintWriter jerr = new PrintWriter(System.err, true);
static BufferedReader jin = new
BufferedReader(new InputStreamReader(System.in));
public static void main(String [] args) {
double a, b;
jout.print("eq1_err: Solving 'ax+b=0'
with NumberFormatException -(c) ~/2A \n");
try {
if (args.length == 0) {
jout.println("Under normal use,
type: java eq1_err
value_a value_b !");
jout.println("Enter a
and b ('aaa' or
'bbb' to throw an exception)
:");
a = Double.parseDouble(jin.readLine());
b = Double.parseDouble(jin.readLine());
}
else
{
a = Double.parseDouble(args[0]);
b = Double.parseDouble(args[1]);
}
jout.println("Solution x = "+
(-b)/a +'\n');
}
catch (IOException ioe) {
jerr.println("IOException warning
: " + ioe.getMessage() +" !");
jerr.println("Stack : ");
ioe.printStackTrace();
}
catch (NumberFormatException nfe) {
jerr.println("NumberFormatException warning : " + nfe.getMessage() +" !");
jerr.println("Stack : ");
nfe.printStackTrace();
jerr.println("\nBy default, a =
0.0 and b = -1.0 !");
a = 0d; b= -1d;
jerr.println("By default x =
"+ (-b)/a +'\n');
}
finally {
jout.println("Done !");
}
es.attente();
}
}
// Autre exemple d’emploi : cf. facrec.java
2.2 Le mot
clé 'throws' pour relayer une exception
====================================================
Pour qu'une méthode puisse déléguer le traitement d'une exception, à un SSP appelant, elle doit le signaler par
une clause 'throws' dans son
en-tête de la manière suivante:
[modificateurDeVisibilité] [TypeC]
uneMéthode([listeDAguments])
throws uneClasseDException,
uneAutreClasseDException ...
{
// bloc d'instructions correspondant ...
}
Ainsi, on peut "externaliser" le
traitement d'exception et le confier à un gestionnaire d'exception dédié se
trouvant dans le(s) sous-programme(s) appelant(s) tant que la pile d'exécution
n'est pas vide. On parle alors de
remontée (ou propagation) d'exception de la méthode appelée vers la méthode
appelante. Si aucune méthode de la pile ne capture l'exception, celle-ci provoque la fin de l'exécution et la
génération d'un message d'erreur.
2.3 Le mot clé
'throw' pour lever une
exception
=============================================
Pour provoquer une exception, on emploie la clause 'throw' (sans 's' ici) de la façon suivante:
throw new uneClasseDException(uneListeDArguments);
Exemple d'emploi de 'throw' cf. FactRexI.java
---------------------------
3. Classification des exceptions
********************************
On a distingué, lors de l'introduction, les deux types d'exceptions:
- les exceptions non contrôlées dérivant
de 'RuntimeException' ou 'Error';
- les autres exceptions, dites exceptions contrôlées.
3.1 Exemple d'erreur
dérivant de la classe 'java.lang.Error'
------------------------------------------------------------
Ce sont des exceptions très
sérieuses liées à la machine virtuelle,
pour lesquelles il est souvent impossible de trouver une solution. C'est
la raison pour laquelle elles ne sont ni ne doivent pas être interceptées par
la clause 'catch' en général, sauf si on souhaite évaluer la taille mémoire
allouée pour la pile d’exécution appelée aussi « pile système ». A
titre d'exemple, on réalise une boucle "infinie" d'appels récursifs
i.e. sans condition d'arrêt (c'est ce qu'il faut TOUJOURS adjoindre en
pratique):
class rec_infi {// Récursivité infinie pour faire déborder
la pile
// static int
compt = 0;
static void methodeRecursive() {
// compt++;
// System.out.println("Appel No
"+compt);
methodeRecursive();
}
public static void main(String[] a) {
rec_infi.methodeRecursive();
}
}
A l'exécution de la machine virtuelle, on
rencontre l'erreur suivante:
Exception in thread "main" java.lang.StackOverflowError
at
rec_infi.methodeRecursive(rec_infi.java : 6)
at
rec_infi.methodeRecursive(rec_infi.java : 6)
...
EXERCICE 2:
Comment déterminer le nombre d'appels récursifs qui provoque le débordement de
la pile ainsi que la taille en Ko de taille de pile système (A terminer en TPP)
?
3.2 Exemple d'exceptions
dérivant de la classe 'RuntimeException'
-----------------------------------------------------------------
L'exemple ci-dessous illustre trois exceptions fréquemment rencontrées. L'emploi de 'try ... catch'
n'est pas nécessaire ici, sauf pour pouvoir enchaîner toutes les erreurs au
sein d'une seule et même trace d'exécution.
class run_err
{ // Quelques exceptions non controlees du Runtime
public static void main(String
args[]) {
System.out.println("run_err:
Verif. de qq exceptions du Runtime");
System.out.println("------- (c)~/2A env.
2002.11.04 21H50");
// ArrayIndexOutOfBoundsException
char [] tab =
{'I','n','f','o','r','m','a','t','i','q','u','e'};
try
{
for (int i=0; i<= tab.length; i++)
System.out.print(tab[i]+" ");
}
catch (ArrayIndexOutOfBoundsException
excep) {
System.err.println("\nErreur :
"+ excep);
}
// StringIndexOutOfBoundsException
String palin = "
try
{
System.out.println("\npalindrome :
"+ palin.toString() );
System.out.println(palin.charAt(
palin.length() ));
}
catch (StringIndexOutOfBoundsException
excep) {
System.err.println("Erreur :
"+ excep);
}
// NullPointerException
String txt = null;
try
{
System.out.println("txt :
"+txt.toString());
}
catch (NullPointerException excep) {
System.err.println("Erreur : "+ excep);
}
es.attente(); // Pour stabiliser l'écran avant de quitter ...
}
}
3.3 Un exemple d'exception
contrôlée: 'FileNotFoundException'
------------------------------------------------------------
EXERCICE 3:
Faire l'analyse du programme ci-dessous (cf. 'fnf_err.java')
en notant les trois emplois
de "throws IOException"
import java.io.*;
class fnf_err {
private BufferedReader jin = null;
private Reader data = null;
private String fileName;
fnf_err() throws IOException
{
jin = new BufferedReader(new
InputStreamReader(System.in));
}
public void setFileName() throws IOException {
if (jin != null)
fileName = jin.readLine();
}
public static void main(String args[]) throws IOException {
PrintWriter jout = new
PrintWriter(System.out, true);
fnf_err unObjet = new fnf_err();
jout.println("Verif de FileNotFoundException -
D'apres Leduc & Leduc");
jout.println("----- p. 96 Ed. Technip
2001 (c)~/2A env. 2002.11.04");
while (unObjet.data == null) {
jout.println("\nEntrer un nom de fichier : ");
unObjet.setFileName();
try
{
unObjet.data = new
FileReader(unObjet.fileName);
}
catch
(FileNotFoundException
excep) {
System.out.println( excep.getMessage()
);
}
}
jout.println("OK, le fichier existe ! A bientot ...");
es.attente();
}
}
Indication:
L'exception de type 'IOException' peut être levée par le constructeur sans argument 'fnf_err()' et par
la méthode 'setFileName()'. Elle n'est pas interceptée immédiatement par ces
méthodes mais transmise à l'appelant qui n'est autre que la méthode principale 'main'. Dans ce cas précis, le 'main' ne
l'intercepte pas non plus et, en cas
d'incident, l'exception provoque alors une interruption de l'exécution.
4. Enrichissement de la hiérarchie des classes
d'exceptions
***********************************************************
Tout programmeur Java peut enrichir la hiérarchie des classes
d'exception en créant sa propre classe qui est toujours une sous-classe de
'Exception' selon l'exemple ci-après (cf. 'lever_ex.java').
On peut hériter ainsi des deux
constructeurs et des méthodes:
-
fillInStackTrace(),
-
getLocalizedMessage(),
-
getMessage(),
-
printStackTrace() et
-
toString()
de la classe 'Throwable'. Une fois la
nouvelle classe d'exception implémentée, on l'instancie (via 'new') et on peut
la lancer (via la clause 'throw') comme dans le corps de la
"methode_aa()" ci-dessous:
class maGestionDException
extends Exception {
maGestionDException() { //
constructeur par défaut ...
}
maGestionDException(String unMessage) {
super(unMessage);
}
}
class lever_ex
{
lever_ex()
throws maGestionDException {
methode_a();
}
void methode_a() throws maGestionDException {
methode_aa();
}
void methode_aa() throws maGestionDException {
throw new maGestionDException("Voici le descriptif : ...");
}
public static void main (String [] arg) {
try
{
new leve_ex();
}
catch (maGestionDException excep_interceptee) {
excep_interceptee.printStackTrace();
}
}
}
Autre exemple: cf. FactRexI.java
5.
Annexe: Une arborescence partielle de la classe
'Throwable'
**************************************************************
Une
version plus détaillée de la hiérarchie des classes d'erreurs et d'exceptions
est donnée par (cf. la documentation de l'API pour le reste des sous-classes):
Object
------
|
|
java.lang.Throwable
|
| java.lang.Error
| |
|
| java.awt.AWTError
| |
| |
java.lang.LinkageError
| | | java.lang.ClassCirculariryError
| | | java.lang.ClassFormatError
| | | java.lang.ExceptionInInitializerError
| | | java.lang.IncompatibleClassChangeError
| | |
| java.lang.AbstractMethodError
| | |
| java.lang.IllegalAccessError
| | |
| // tentative d'accès est interdite
| | |
| java.lang.InstantiationError
| | |
| java.lang.NoSuchFieldError
| | |
| java.lang.NoSuchMethodError
| | |
| //la méthode référencée n'est pas accessible - Ex. :
main
| | |
| | | java.lang.NoClassDefFoundError
| | | java.lang.UnsatisfiedLinkError
| | | java.lang.VerifyError
| |
| | java.lang.ThreadDead
| |
| |
java.lang.VirtualMachineError
|
| | java.lang.InternalError
| | | java.lang.OutOfMemoryError
| | | // allocation impossible par
manque de mémoire
| | | java.lang.StackOverflowError
| | | // il y a débordement de la pile
système
| | | java.lang.UnknownError
|
| java.lang.Exception
|
| java.awt.AWTException
|
java.swing.text.BadLocationException
| java.lang.ClassNotFoundException
| java.lang.CloneNotSupportedException
|
java.util.zip.DataFormatException
|
java.security.GeneralSecurityException
| java.lang.IllegalAccessException
| java.lang.InstantiationException
|
java.lang.InterruptedException
|
| java.io.IOException
| | javax.swing.text.ChangedCharSetException
| | java.io.CharConversionException
| | java.io.EOFException
| | java.io.FileNotFoundException //
fic. manquant
| | java.io.InterruptedIOException
| | java.net.MalformedURLException
| | java.io.ObjectStreamException
| | java.net.ProtocolException
| | java.rmi.RemoteException
| | java.net.SocketException
| | java.io.SyncFailedException
| | java.net.UnknownHostException
| |
java.net.UnknownServiceException
| | java.io.UnsupportedEncodingException
| | java.io.UTFDataFormatException
| | java.util.zip.ZipException
|
| javax.naming.NamingException
| java.lang.NoSuchFieldException
| java.lang.NoSuchMethodException
| java.rmi.NotBoundException
|
java.text.ParseException
| java.awt.print.PrinterException
| java.beans.PropertyVetoException
|
| java.lang.RuntimeException
| | java.lang.ArithmeticException
| | // erreur arithmétique est survenue
(Ex.: DIV par 0)
| | java.lang.ArrayStoreException
| | java.lang.ClassCastException
| |
java.lang.IllegalArgumentException
| |
| java.lang.IllegalThreadStateException
| |
| java.lang.NumberFormatException
| |
| // Ex.: une chaîne de car. est incorrecte pour
| |
| // représenter un
nombre
| |
| | java.lang.IllegalMonitorStateException
| | java.lang.IllegalStateException
| | java.lang.IndexOutOfBoundsException
| |
| java.lang.ArrayIndexOutOfBoundsException
| |
| // indice d'un tableau est en dehors des bornes
| |
| java.lang.StringIndexOutOfBoundsException
| |
// indice de chaîne est en dehors des bornes
| | java.lang.NegativeArraySizeException
| | java.lang.NoSuchElementException
| |
| | java.lang.NullPointerException
| | // tentative d'accès à un objet
avec référence 'null'
| |
| | java.lang.SecurityException
| | java.lang.UnsupportedOperationException
| | java.util.EmptyStackException
| | java.util.NoSuchElementException
| |
| java.util.InputMismatchException
// cf. Java 5
|
|
| java.sql.SQLException
Les traits continus correspondent au mot
clé 'extends' pour signifier qu'une sous-classe "dérive de" sa
super-classe en mode d'héritage simple. Toutes
les sous-classes ci-dessus implémentent aussi la classe 'Serializable'. Les exceptions en
caractère gras sont les plus souvent commises en 2A.