Les exceptions


Dans le cours d'introduction, nous avions déjà parlé des différents types d'erreur pouvant se produire en programmation et en particulier des erreurs d'exécution.

Nous allons voir ici comment protéger un programme contre ce type d'erreur avec une nouvelle structure de contrôle. En principe cette partie du cours aurait donc plutot eu sa place dans le cours consacré aux structures de contrôle, mais comme nous allons le voir ici, cette nouvelle structure de contrôle utilise des notions de programmation objet. Je ne pouvais donc pas vous en parler plus tot !

La classe Exception

Les erreurs d'exécution sont représentées en Java par la classe Exception et ses sous-classes. Lorsqu'une erreur d'exécution se produit, la JVM génère automatiquement une exception, c'est à dire une instance d'une de ces classes. En principe, cela provoque un arrêt brutal du programme avec l'affichage d'un message d'erreur mentionnant le type d'exception. Nous avons représenté ci-dessous les relations d'héritage de quelques unes de ces classes (exceptions usuelles):

Exception.jpg, 89kB

Les flèches en pointillées représentent des relations d'héritage indirectes (c.a.d qu'il y a d'autres classes intermédiaires qui ont été omises pour simplifier le schéma).

Pour pouvoir traiter les exceptions de la classe IOException, il est nécessaire d'importer le package java.io, en faisant figurer l'instruction suivante au début de votre fichier source:

  import java.io.*;

Sans cette instruction, la classe IOException (ainsi que toute autre classe du package java.io) ne serait pas reconnu par le compilateur, à moins d'écrire java.io.IOException à la place de IOException dans tout votre fichier source.

Nous rencontrons ici pour la première fois le problème d'importation des packages. Certaines classes prédéfinies de Java appartiennent à des packages qui sont importés par défaut. Mais certains packages ne sont pas systématiquement importés. Si vous ne les importez pas, le compilateur ne reconnaitra pas les classes appartenant à ces packages (à moins que vous ne préfixiez le nom de ces classes par le nom du package).

La structure de controle try ... catch

La structure de contrôle try ... catch est spécialement concue pour traiter les erreurs d'exécution et ainsi, éviter l'arrêt de fonctionnement du programme. Voici sa syntaxe:

try {

   Instructions pouvant provoquer une erreur d'exécution.

 }
catch (c1  e)
 {
     traitement de l'exception de la classe c1
 }
catch (c2  e)
 {
     traitement de l'exception de la classe c2
 }
 .
 .
catch (cn  e)
 {
     traitement de l'exception de la classe cn
 }

Le principe de fonctionnement de cette structure de contrôle est le suivant: s'il se produit une erreur d'exécution lors de l'exécution des instructions (celles qui figurent juste après try, dans les accolades), la JVM exécute le bloc catch associé à la classe à laquelle appartient l'exception correspondante.

Lors de l'exécution de ce bloc, l'exception générée est passée en paramètre (le paramètre formel e) du bloc. Un bloc catch fonctionne donc comme un sous-programme.

L'intéret du paramètre e est de pouvoir utiliser les méthodes de la classe Exception pour obtenir plus d'information sur l'erreur qui s'est produite, ou pour en donner à l'utilisateur. Par contre, vous n'êtes pas obligé de l'utiliser pour traiter l'exception.

Exemple

A titre d'exemple, reprenons le projet Calculatrice (cf. exercices du cours premières notions) en supposant que la variable X est déclarée en int. Nous avons vu dans cet exercice, qu'il était possible de provoquer une erreur d'exécution de deux manières.

Voici la première: (Nous avons agrandi l'image pour que le message d'erreur soit lisible. Pour mieux voir, agrandissez le cadre gauche ou déplacez la barre de défilement horizontal en bas du cadre gauche)

NumberFormatException.jpg, 71kB

Ici, l'utilisateur a produit une écriture incorrecte pour un nombre entier: la chaine "6.6.1961". Lorsqu'il a voulu élever ce nombre erroné au carré à l'aide de la touche correspondante, cela a provoqué une erreur d'exécution du type NumberFormatException. Le programme s'est arrêté de fonctionner et la JVM a affiché un message d'erreur.

Et voici la deuxième manière de provoquer une erreur d'exécution:

ArithmeticException.jpg, 58kB

Ici, l'utilisateur à voulu calculer l'inverse de 0. Cela a provoqué une erreur du type ArithmeticException.

Dans les exemples de projet de ce cours, vous trouverez une version améliorée de ce projet, dans laquelle les erreurs d'exécutions précédentes sont interceptées.

Voici, le comportement du programme amélioré avec le "nombre" 6.6.1961:

Catch-NumberFormatException.jpg, 25kB

Un message d'erreur destiné à l'utilisateur est affiché, lui signalant que le nombre saisi est incorrect. A la différence de la version précédente du projet, aucun message d'erreur n'est affiché par la JVM et le message affiché est plus explicite. L'utilisateur lambda comprendra mieux l'origine du problème que s'il avait simplement obtenu le message d'erreur standard de la JVM.

Et voici le comportement du programme lorsque l'utilisateur tente de calculer l'inverse de 0:

Catch-ArithmeticException.jpg, 24kB

Voyons à présent comment le code du projet a été modifié pour obtenir ce nouveau comportement.

Nous avons simplement modifié les procédures évènementielles associées aux deux touches de calcul. Voici celle de la touche 1/x :

 private void BT_InverseActionPerformed(...) {                                           
      
try {
  X = es.LireEntier(CT_Nombre);
  X = 1 / X;
  es.Afficher(X, CT_Nombre); Affichage = "";
  }
  catch (NumberFormatException e)
  {
      es.AfficherMessage("Le nombre saisi est incorrect");
  }
  catch (ArithmeticException e)
  {
      es.AfficherMessage("Opération mathématique non valide !");   
  }      
}     

Ici, les deux types d'exception pouvant se produire sont traitées.

Dans la procédure évènementielle de la touche X2, l'exception provenant de la division par zéro ne peut pas se produire. Nous n'avons donc traitée que l'exception NumberFormatException:

private void BT_CarréActionPerformed(...) {                                         
try {
  X = es.LireEntier(CT_Nombre);
  X = X * X;
  es.Afficher(X, CT_Nombre); Affichage = "";
  }
  catch (NumberFormatException e)
  {
      es.AfficherMessage("Le nombre saisi est incorrect");
  }
}