TUTORIELS 
Les "exceptions" en Java : le bloc "try / catch / finally"
Nous poursuivons notre étude de la gestions des erreurs grâce aux mécanismes des exceptions. A l'honneur aujourd'hui le bloc "try/catch/finally".  (16 janvier 2002)
 

Dans notre précédente introduction aux exceptions en Java, nous avions jeté les bases de ces mécanismes notamment à l'aide d'un exemple comportant une division par zéro. Ce calcul provoquait alors une exception, de type "ArithmeticException".

Nous reprenons aujourd'hui cet exemple en l'implémentant afin d'en savoir un peu plus sur le bloc "try / catch / finally" que nous avions abordé en fin du tutoriel précédent.


Catch multiples et bloc finally

Pour rappel, voici à quoi ressemblait dans notre exemple :

(ZeroDivide.java)
public class ZeroDivide
{
     static public void main(String[] args)
     {
          int a = 3;
          int b = 0;

          try
          {
               System.out.println("Resultat de la division : " + a/b);
               System.out.println("Instructions suivant la division...");
          }
          catch(ArithmeticException e)
          {
               System.out.println("Une exception s'est produite ! (ArithmeticException)");
          }

          System.out.println("Instructions qui suivent le bloc catch...");
     }
}

Nous obtenions à l'affichage :

Une exception s'est produite ! (ArithmeticException)
Instructions qui suivent le bloc catch...


Voyons ce que donne un bloc "finally" avec ce même exemple. L'intérêt d'un tel bloc repose sur le fait que les instructions qu'il comporte seront toujours exécutées, qu'une exception soit levée ou pas. Il permet donc de s'assurer par exemple qu'un fichier ouvert dans le bloc try sera systématiquement refermé, quoiqu'il arrive, grâce au bloc "finally".
Au vu des résultats fournis par l'exemple précédent (le traitement continue après le bloc "catch"), on peut se demander pourquoi ne pas mettre ces instructions de fermeture de fichier (en prenant le même exemple) dans le bloc catch ?
En fait plusieurs blocs catch sont susceptibles de se suivre et donc de correspondre au même bloc try. Il serait redondant de placer son code de fermeture de fichier dans chacun des blocs catch correspondant au bloc try...

A propos de ces blocs "catch" qui se suivent, il faut prendre garde à l'ordre dans lequel on les place : toujours du plus "particulier" au plus "général".

Admettons que nous possédions la configuration suivante :

catch(ArithmeticException e)
{
     ...
}

catch(IndexOutofBounds e)
{
     ...
}

catch(Exception e)
{
     ...
}


Si les deux premiers catch peuvent être intervertis sans problème (ils sont spécialisés dans un type d'erreur précis), le dernier représente la classe Exception, il est donc susceptible de recevoir la plupart des exceptions qui nous intéressent !
De manière générale si la classe d'un catch dérive de celle d'un autre catch, elle doit être placée devant cette dernière.
Inutile de vivre dans l'angoisse d'oublier ce dernier principe, le compilateur lui n'oublie pas, et saura vous le rappeler. Ainsi dans l'exemple précédent, si le catch (Exception e) n'est pas placé en dernier, le code ne se compilera pas.

En ce qui concerne notre bloc finally, sa syntaxe est des plus simples :

try
{
     System.out.println("Resultat de la division : " + a/b);
     System.out.println("Instructions suivant la division...");
}
catch(ArithmeticException e)
{
     System.out.println("Une exception s'est produite ! (ArithmeticException)");
}
finally
{
     System.out.println("Bloc Finally\n");
}

System.out.println("Instructions qui suivent le bloc finally...");

A l'affichage nous obtenons :

Une exception s'est produite ! (ArithmeticException)
Bloc Finally
Instructions qui suivent le bloc finally...


Lorsque l'exception est levée, la main passe au bloc catch puis au bloc finally. Si le bloc try ou lui-même ne comportent pas d'instruction "return", le programme se poursuit avec l'instruction qui suit le bloc finally.

Attention, de même que pour le bloc catch, le bloc finally doit correspondre à un bloc try en particulier, les blocs try, catch et finally doivent se suivre sans code intermédiaire entre eux.

Nous évoquions l'instruction "return", voyons ce que son utilisation provoque si on la place à la fin du bloc finally:

try {
     ...
}
catch(ArithmeticException e) {
     ...
}
finally {
     System.out.println("Bloc Finally\n");
     return;
}

System.out.println("Instructions qui suivent le bloc catch...");

Nous obtenons une erreur de compilation :

ZeroDivide.java:25: unreachable statement
System.out.println("Instructions qui suivent le bloc finally...");

Le compilateur a détecté que le code ne passera jamais par l'affichage "instructions qui suivent le bloc finally", et nous le signale.


Propagation d'une exception


Que se passe t'il lorsque le bloc catch chargé de capturer l'exception n'est pas à proximité de la méthode à l'origine de l'erreur ?
Si nous souhaitons voir les différentes méthodes impliquées se transmettre l'exception, il faut adopter la syntaxe suivante, par exemple :

public static int essai1(int c, int d) throws ArithmeticException

Signifie que la méthode essai1 est susceptible de générer une exception de type "Arithmetic Exception". Ce type de déclaration est obligatoire pour les classes dont la classe de base n'est pas "RuntimeException" (cf tutoriel précédent).

Le code source suivant utilise plusieurs méthodes dans le seul but "d'éloigner" la méthode à l'origine de l'erreur du catch concerné, pourtant cela fonctionne :

(ZeroDivide.java)
public class ZeroDivide
{
static public void main(String[] args)
{
int a = 6;
int b = 0;
int z = 0;

try {
     System.out.println("Avant la division...");
     z = essai1(a, b);
     System.out.println("Résultat de la division : " + z);
}

catch(ArithmeticException e) {
     System.out.println("Une exception s'est produite ! (ArithmeticException)");
}

finally {
     System.out.println("Bloc Finally\n");
}

System.out.println("Instructions qui suivent le bloc catch...");
}

public static int essai1(int c, int d) throws ArithmeticException
{
     return essai2(c,d);
}

public static int essai2(int e, int f) throws ArithmeticException
{
     return essai3(e,f);
}

public static int essai3(int g, int h) throws ArithmeticException
{
     return (g/h);
}
}

C'est bien dans la méthode "essai3" que l'exception est levée mais celle-ci est propagée de méthodes en méthodes jusqu'à ce qu'elle parvienne au catch chargée de la gérer.

Nous obtenons l'affichage suivant :

Avant la division...
Une exception s'est produite ! (ArithmeticException)
Bloc Finally

Pour voir ce code fonctionner correctement il suffit de modifier la valeur de "b" par 3 par exemple. Cependant nous souhaitons en savoir plus sur cette erreur. Il existe plusieurs méthodes susceptibles de nous aider, en voici deux : getMessage() et printStackTrace(). La première décrit l'exception en cours, la seconde envoie la trace de la pile au flux de sortie standard, nous allons le voir. Ces méthodes sont issues de la classe Throwable, nous exploitons ici l'objet transmis au bloc catch :

catch(ArithmeticException e)
{
     System.out.println("Une exception s'est produite ! (ArithmeticException)" +      e.getMessage());
     System.out.println("Affichage de la pile :\n");
     e.printStackTrace();
}


Nous obtenons à l'écran :

Avant la division...
Une exception s'est produite ! (ArithmeticException) / by zero
Affichage de la pile :
java.lang.ArithmeticException: / by zero
at ZeroDivide.essai3(ZeroDivide.java:41)
at ZeroDivide.essai2(ZeroDivide.java:36)
at ZeroDivide.essai1(ZeroDivide.java:31)
at ZeroDivide.main(ZeroDivide.java:12)
Bloc Finally
Instructions qui suivent le bloc catch...

getMessage() apporte le " / by zero" tandis que printStackTrace facilite le travail de débogage en précisant le chemin parcouru par l'exception.


Dans un prochain tutoriel nous verrons comment lever nos propres exceptions.

 
[ Arnaud GadalJDNet
 
Accueil | Haut de page