LECON 206


Les exceptions
Voici encore une notion très importante en programmation Java.
Exception : erreur se produisant dans un programme conduisant le plus souvent à l'arrêt de celui-ci.

Il vous est sûrement déjà arrivé d'avoir un gros message affiché en rouge dans la console d'eclipse : eh bien ceci a été généré par une exception... qui n'a pas été capturée. La gestion des exceptions s'appelle aussi la capture d'exception !

Le principe consiste à repérer un morceau de code qui pourrait générer une exception (une division par zéro, par exemple), de capturer l'exception correspondante, et enfin de gérer celle-ci, c'est-à-dire d'afficher un message personnalisé et de continuer le traitement.

Bon : vous voyez maintenant ce que nous allons aborder durant ce chapitre... Donc, allons-y !

Sommaire du chapitre :

  • Premier exemple d'exception et le bloc try{....} catch{...}
  • Les exceptions personnalisées
  • La gestion de plusieurs exceptions
  • Astuce d'Eclipse
  • Ce qu'il faut retenir


Premier exemple d'exception et le bloc try{....} catch{...}

Pour vous faire comprendre le principe des exceptions, je dois tout d'abord vous dire que Java contient une classe nommée Exception, où sont répertoriés différents cas d'erreurs. La division par zéro dont je vous parlais tout à l'heure en fait partie !
Toutes les classes de Java possèdent des exceptions : par exemple, la classe java.io, qui gère les entrées - sorties, a, entre autres, l'exception IOException.


Je ne peux malheureusement pas vous en faire toute la liste, mais.... je peux vous donner une astuce pour savoir de laquelle il s'agit... Et ainsi pouvoir la capturer, et afficher un message personnalisé, sans que votre programme ne s'arrête.

Créez un nouveau projet avec seulement la classe main, et mettez-y le code suivant :

Code : Java -  
1
2
3
int j = 20, i = 0;
System.out.println(j/i);
System.out.println("coucou toi !");

Vous devriez avoir un zoli message d'erreur Java (en rouge) comme celui-ci :


Mais surtout, vous devez vous rendre compte que lorsque l'exception a été levée, le programme s'est arrêté !
Dans ce message, le nom de l'exception qui a été déclenchée est ArithmeticException. Nous savons donc maintenant qu'une division par zéro est une ArithmeticException. Nous allons pouvoir la capturer, et réaliser un traitement en conséquence.
Une exception se capture grâce à un bloc try {ici, le code susceptible de générer une exception} ; le traitement associé, lorsque le bloc try{...} capture une exception, se trouve dans un bloc catch {ici, ce que le programme doit faire si une exception est capturée}.


Ce que je vous propose maintenant, c'est de capturer l'exception de notre division par zéro, et d'afficher un message personnalisé. Pour ce faire, tapez le code suivant dans votre main :

Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class Test {
 
        /**
         * @param args
         */
        public static void main(String[] args) {
                
                int j = 20, i = 0;
                try {
                        System.out.println(j/i);
                } catch (ArithmeticException e) {
                        // TODO Auto-generated catch block
                        System.out.println("Division par zéro !");
                }
                System.out.println("coucou toi !");
        }
 
}


Si vous exécutez ce code, vous devez avoir ceci sous les yeux :



Je tiens tout d'abord à vous féliciter : vous venez de capturer votre première exception en Java !

Voyons un peu ce qui se passe :
  • nous initialisons deux variables de type int, l'une à 0, et l'autre à un chiffre quelconque.
  • Ensuite, nous isolons le code susceptible de générer une exception, ici : System.out.println(j/i);.
  • Lorsque le programme atteint cette ligne, une exception de type ArithmeticException est levée.
  • Notre bloc catch contient justement un objet de type ArithmeticException en paramètre. Nous l'avons appelé e.
  • L'exception étant capturée, l'instruction du bloc catch s'exécute !
  • Notre message d'erreur personnalisé s'affiche alors à l'écran.

Lorsque nous capturons une exception, nous pouvons aussi dire que nous levons une exception...

D'accord, mais le paramètre de notre bloc catch, il sert à quoi, au juste ?


Il sert à savoir quel type d'exception doit être capturé. Et l'objet -ici, e- peut servir à agrémenter notre message, grâce à l'appel de la méthode getMessage().
Faites à nouveau ce même test, en remplaçant l'instruction du catch par celle-ci :

Code : Java -  
1
System.out.println("Division par zéro !" + e.getMessage());


Vous devez maintenant avoir ceci :


Voilà ce que vous renvoie la fonction getMessage().
Je vous disais aussi que le principe de capture d'exception permettait de ne pas arrêter l'exécution du programme. Et vous avez pu le constater par vous-mêmes !
Lorsque nous capturons une exception, le code présent dans le bloc catch(){...} est exécuté mais le programme poursuit son cours !
Mais ce que vous ignorez sûrement, c'est que nous pouvons créer et intercepter nos propres exceptions !

Si nous passions tout de suite à la prochaine partie de ce chapitre ?

Les exceptions personnalisées

À partir de maintenant, nous allons nous servir à nouveau de notre projet Ville (celui que vous avez utilisé dans les premiers chapitres...).
Nous allons perfectionner un peu la gestion de nos objets Ville et Capitale... Comment ? Eh bien je vois bien une exception qui pourrait être créée... Et je suis sûr que certains petits malins se sont déjà amusés à créer des villes ou des capitales avec un nombres d'habitants négatif....

Je vous propose simplement de mettre en oeuvre une exception de notre cru, ceci afin de pouvoir interdire l'instanciation d'objet Ville ou Capitale ayant un nombre négatif d'habitants.

La procédure pour faire ce tour de force est un peu particulière :
  1. Nous devons créer une classe héritée de la classe Exception : appelons-la NombreHabitantException. Par convention, les exceptions ont un nom se terminant par Exception.
  2. Nous devons renvoyer l'exception levée à notre classe NombreHabitantException.
  3. Ensuite, gérer celle-ci dans notre classe NombreHabitantException.

Comment faire tout ça ?

Eh bien je vais vous apprendre encore deux autres mots clés !
Non, ne grimacez pas... Je vous assure que si vous en retenez un, vous allez retenir l'autre...

Le premier mot clé



throws

Ce mot clé permet de dire à une instruction Java (condition, déclaration de variable...) ou à une classe entière qu'une exception potentielle sera gérée par une classe -souvent une classe personnalisée- mais ce peut être la classe Exception elle-même. Ce mot clé est suivi du nom de la classe qui va gérer l'exception. Ceci a pour but de définir le type d'exception qui risque d'être générée par l'instruction, ou la classe qui précède le mot clé throws.

Le deuxième mot clé



throw

Celui-ci permet d'instancier un objet dans la classe suivant l'instruction throws. Cette instruction est suivie du mot clé new ainsi que d'un objet cité avec throws. En fait, il lance une exception, tout simplement.
Faites surtout bien attention à ne pas confondre ces deux mots clé.


Pour pouvoir mettre en pratique ce système, nous devons commencer par créer une classe qui va gérer nos exceptions. Celle-ci, je vous le rappelle, doit être héritée d'Exception. Pour commencer, inutile de créer un constructeur, ce qui nous donnerait une classe Erreur, héritée de Exception, vide.
Comme ceci :

Code : Java -  
1
2
3
4
5
6
7
class NombreHabitantException extends Exception{
 
   public NombreHabitantException(){
                System.out.println("Vous essayez d'instancier une classe Ville avec un nombre d'habitants négatif !");
        }
  
}

Reprenez votre projet avec vos classes Ville, Capitale et créez maintenant une classe NombreHabitantException, comme je viens de le faire !

Maintenant, c'est dans le constructeur de nos objets que nous allons mettre une condition qui, si elle est remplie, lève une exception de type NombreHabitantException.

En gros, nous devons dire à notre constructeur de ville : "Si l'utilisateur crée une instance ville avec un nombre d'habitants négatif, créer un objet de type NombreHabitantException (hérité d'Exception).

Le constructeur d'initialisation de ville doit ressembler à ce qui suit, maintenant.

Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public Ville(String pNom, int pNbre, String pPays) throws  NombreHabitantException
  {  
          if(pNbre < 0)
                  throw new NombreHabitantException();
          else
          {
                  nbreInstance++;  
                  nbreInstanceBis++;
                  
                  nomVille = pNom;
                  nomPays = pPays;
                  nbreHabitant = pNbre;
                  this.setCategorie();
          }
  }

throws NombreHabitantException nous indique que si une erreur est capturée, celle-ci sera traitée en tant qu'objet de la classe NombreHabitantException ! Ce qui, au final, nous renseigne sur le type de l'erreur en question.

throw new NombreHabitantException(); instancie la classe NombreHabitantException si la condition if(nbre < 0) est remplie.

Maintenant que vous avez fait cette petite modification, retournez dans votre classe main, effacez son contenu, puis créez un objet ville de votre choix !
Et à présent, vous devez voir une erreur persistante ; c'est tout à fait normal, ce qui doit vous donner ceci :


Ceci signifie qu'à partir de maintenant, dû aux changements dans le constructeur, vous devrez gérer les exceptions possibles sur cette instruction. Avec un bloc try{} catch{}.
On dit aussi que votre constructeur est devenu une méthode à risque, et vous avez laissé le soin au développeur de gérer l'exception potentielle !


Donc, pour que l'erreur disparaisse, il nous faut entourer notre instanciation avec un bloc try{...}catch{...}.
Comme ceci :


Vous pouvez constater que l'erreur a disparu et que notre code compile et s'exécute correctement.
Par contre, il faut que vous soyez préparés à une chose. Le code que j'ai utilisé ci-dessus fonctionne très bien, mais ce code :

Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Sdz1 {
 
        public static void main(String[] args)
        {
                try {                   
                        Ville v = new Ville("Rennes", 12000, "France");         
                } catch (NombreHabitantException e) {}
        
                System.out.println(v.toString());
        }       
          
}


ne fonctionnera pas et pour appuyer mes dires, voici le témoignage de quelqu'un d'intègre :


Vous pouvez constater qu'Eclipse n'aime pas du tout notre code !
Pourquoi cela ?

Tout simplement car la déclaration de votre objet Ville est faite dans un sous-bloc d'instructions, celui du bloc try{}. Et rappelez-vous :
Une variable déclarée dans un bloc d'instructions n'existe que dans ce bloc d'instructions !

Donc, ici, notre objet v, n'existe pas en dehors de l'instruction try{}. Pour pallier ce problème, il nous suffit de déclarer notre objet en dehors du bloc try{} et de l'instancier à l'intérieur !

Nous pouvons faire ceci :
Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Sdz1 {
 
        public static void main(String[] args)
        {
                Ville v = null;
                try {                   
                        v = new Ville("Rennes", 12000, "France");               
                } catch (NombreHabitantException e) {}
        
                System.out.println(v.toString());
        }       
          
}


Et ce code nous donne :
Code : Console -  
Rennes est une ville de France, elle comporte : 12000 => elle est donc de catégorie : C


Mais si nous déclarons une Ville avec un nombre d'habitants négatif pour tester notre exception ?
Avec ce code, par exemple :
Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Sdz1 {
 
        public static void main(String[] args)
        {
                Ville v = null;
                try {                   
                        v = new Ville("Rennes", -12000, "France");               
                } catch (NombreHabitantException e) {}
        
                System.out.println(v.toString());
        }       
          
}

Voici ce que nous obtenons :


Voyons ce qu'il s'est passé.
  • Nous avons bien déclaré notre objet en dehors du bloc d'instructions.
  • Au moment d'instancier celui-ci, une exception est levée ! L'instanciation échoue lamentablement !
  • La clause catch{} est exécutée et notre objet NombreHabitantException est instancié. Écriture du message.
  • Et lorsque nous arrivons sur l'instruction "System.out.println(v.toString());", notre objet est null !
  • Une NullPointerException est levée !



Ce qui signifie que si notre instanciation a échoué dans notre bloc try{}, le programme plantera !
Comment empêcher cela, alors ?

Vous allez voir, c'est très simple. Il suffit d'instancier un objet Ville par défaut dans notre bloc catch{}. Grâce à cela, si notre instanciation avec valeur échoue, on fait une instanciation par défaut qui, elle, n'est pas une méthode à risque !
Voyez plutôt :


Vous pouvez voir que l'exception est bien levée et que notre objet est instancié mais, surtout, que notre programme ne plante plus !

Maintenant que nous avons vu la création d'exception, il serait de bon ton que nous puissions avoir de plus amples renseignements concernant celle-ci.
Par exemple, il serait peut-être bon de réafficher le nombre d'habitants que l'objet a reçu...
Pour faire ceci, nous n'avons qu'à créer un deuxième constructeur dans notre classe NombreHabitantException, qui prend un nombre d'habitants en paramètre. Un peu comme ça :
Code : Java -  
1
2
3
4
5
public NombreHabitantException(int nbre)
{
    System.out.println("Instanciation avec un nombre d'habitants négatif");
    System.out.println("\t => " + nbre);
}


Il vous suffit maintenant de définir cette construction de notre objet hérité d'Exception dans votre classe Ville. Comme ça :
Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public Ville(String pNom, int pNbre, String pPays) throws  NombreHabitantException
  {  
          if(pNbre < 0)
                  throw new NombreHabitantException(pNbre); // on appelle le nouveau constructeur
          else
          {
                  nbreInstance++;  
                  nbreInstanceBis++;
                  
                  nomVille = pNom;
                  nomPays = pPays;
                  nbreHabitant = pNbre;
                  this.setCategorie();
          }
          
  }


Et si vous exécutez le même code que précédemment, vous obtiendrez ceci :


C'est pas mal, avouez-le !
Mais vous devez savoir que l'objet passé en paramètre de la clause catch a des méthodes héritées de la classe Exception. Regardez :


Vous pouvez les utiliser si vous le voulez et surtout, si vous en avez l'utilité...
Nous utiliserons certaines de ces méthodes dans les prochains chapitres...
Ici, la méthode printStackTrace() permet de voir ou se situe l'exception dans notre code ! Elle vous informe sur le nom de la classe levant l'exception et le numéro de ligne ou se trouve le code l'ayant levée.


Je vais vous faire peur : ici, nous avons capturé une exception mais nous pouvons en capturer plusieurs...

La gestion de plusieurs exceptions

Bien entendu, ceci est valable pour toutes sortes d'exceptions, qu'elles soient personnalisées, ou faisant partie de Java !
Disons que nous voulons lever une exception, si le nom de la ville fait moins de 3 caractères.

Nous allons répéter les premières étapes vues précédemment, c'est-à-dire créer une classe NomVilleException:

Code : Java -  
1
2
3
4
5
6
7
public class NomVilleException extends Exception {
 
        public NomVilleException(String message){
                super(message);
        }
        
}


Vous avez remarqué que nous avons utilisé super. Avec cette redéfinition, nous pourrons afficher notre message d'erreur en utilisant la méthode getMessage().
Vous allez voir.

Maintenant ajoutez une condition dans notre constructeur Ville :
Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public Ville(String pNom, int pNbre, String pPays) throws  NombreHabitantException, NomVilleException
  {  
          if(pNbre < 0)
                  throw new NombreHabitantException(pNbre);
          
          if(pNom.length() < 3)
                  throw new NomVilleException("le nom de la ville est inférieur à 3 caractères ! nom = " + pNom);
          else
          {
                  nbreInstance++;  
                  nbreInstanceBis++;
                  
                  nomVille = pNom;
                  nomPays = pPays;
                  nbreHabitant = pNbre;
                  this.setCategorie();
          }
          
  }


Vous remarquez que les différentes erreurs dans l'instruction throws sont séparées par une virgule !
Maintenant, nous sommes parés pour la capture de deux exceptions personnalisées. Regardez comment on gère deux exceptions sur une instruction :

Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Ville v = null;
                try {                   
                        v = new Ville("Re", 12000, "France");           
                }
                //Gestion de l'exception sur le nombre d'habitants
                catch (NombreHabitantException e) {
                        e.printStackTrace();
                        v = new Ville();
                }
                //Gestion de l'exception sur le nom de la ville
                catch(NomVilleException e2){
                        System.out.println(e2.getMessage());
                        v = new Ville();
                }
        
                System.out.println(v.toString());

Vous pouvez voir comment utiliser la méthode getMessage() à présent. C'est tout bête, avouez-le !


Vous pouvez constater qu'un deuxième bloc catch{} s'est glissé... Eh bien c'est comme ceci que nous gérerons plusieurs exceptions !
Vous avez aussi remarqué que j'ai aussi changé le code afin que l'exception sur le nom soit levée et non plus l'exception sur le nombre d'habitants...

Vous aurez ceci :



Si vous mettez un nom de ville de moins de 3 caractères, et un nombre d'habitants négatif, c'est l'exception du nombre d'habitants qui sera levée en premier ! Et pour cause... il s'agit de notre première condition dans notre constructeur...
Lorsque plusieurs exceptions sont gérées par une portion de code, pensez bien à mettre les blocs catch du plus pertinent au moins pertinent. En fait, il s'agit des erreurs capturées à mettre par ordre de pertinence. Dans notre premier exemple d'exception, sur la division par zéro, si nous avions mis un bloc catch(Exception ex){} avant le bloc catch(ArithmeticException e){}, une erreur se serait produite à la compilation, car Exception est plus générique que ArithmeticException.


Il y a une instruction dont je ne vous ai pas encore parlé... Il s'agit de la clause finally. Celle-ci est une clause se positionnant après les clauses catch.
En fait, ce qui se trouve dans cette clause sera TOUJOURS exécuté. Qu'une exception soit levée ou non.

Exemple :

Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Sdz1 {
 
        public static void main(String[] args)
        {
                Ville v = null;
                try {                   
                        v = new Ville("Re", 100, "France");             
                }
                //Gestion de l'exception sur le nombre d'habitants
                catch (NombreHabitantException e) {
                        e.printStackTrace();
                        v = new Ville();
                }
                //Gestion de l'exception sur le nom de la ville
                catch(NomVilleException e2){
                        System.out.println(e2.getMessage());
                        v = new Ville();
                }
        
                //La fameuse clause finally
                finally{
                        System.out.println("\n---------------------------------------------");
                        System.out.println("Voici le code qui est toujours exécuté ! ! ! ");
                        System.out.println("---------------------------------------------\n");
                }
                System.out.println(v.toString());
        }                 
}


Quoi qu'il se passe dans la classe try ou catch, les instructions de la clause finally seront TOUJOURS exécutées ! Et vous pouvez faire autant de tests que vous le voulez.
Euh... d'accord. Mais à quoi ça peut bien nous servir ? On ne peut pas mettre l'instanciation de notre objet ici !

Très juste !
Par contre, vous allez apprendre dans les chapitres suivants à ouvrir des flux de données. Ce genre de code regorge d'exceptions en tout genre et vous serez ravis de pouvoir fermer votre flux, quoiqu'il arrive !

Bon : je crois qu'un récapitulatif s'impose mais avant ceci, voici notre rubrique "Astuce d'Eclipse".

Astuce d'Eclipse

Il y a plusieurs manières de procéder, mais toutes font la même chose.
L'astuce ici réside dans le fait de générer les blocs try{} catch{} automatiquement. Bien sûr, il faut que les clauses de déclenchement soient définies au préalable !

Si vous reprenez le code de votre méthode main, si vous effacez le contenu et ajoutez une instanciation de l'objet Ville sans les clauses try{} catch{}, vous avez l'erreur persistante dont je vous parlais au début du chapitre.

Si vous cliquez sur la croix rouge, située à gauche de votre zone de saisie, vous obtenez ceci :


Choisissez l'option Surround with try/catch et vous avez votre code, tout beau tout propre !

La deuxième méthode consiste à   votre (ou vos) ligne(s) de code à risque et de faire : Source / Surround with / try/catch block ou d'utiliser le raccourci clavier Shift + Alt + Z :

Voici l'image en utilisant le menu :


Voici l'image en utilisant le raccourci clavier :



Voilà, maintenant, rendez-vous au topo habituel !

Ce qu'il faut retenir

  • La super classe qui gère les exceptions s'appelle : Exception.
  • Lorsqu'un événement que la JVM ne sait pas gérer apparaît, une exception est levée (ex : division par zéro) !
  • Vous pouvez créer une classe d'exception personnalisée en créant une classe héritant de la classe Exception.
  • L'instruction qui permet de capturer des exceptions est le bloc try{} catch{}.
  • Si une exception est levée dans le bloc try, les instructions figurant dans le bloc catch seront exécutées si celui-ci capture l'exception levée !
  • Vous pouvez ajouter autant de blocs catch que vous le voulez à la suite d'un bloc try. Mais respectez l'ordre de pertinence. Du plus pertinent au moins pertinent !
  • Dans une classe objet, vous pouvez prévenir la JVM qu'une méthode est dite "à risque", ceci grâce au mot clé throws.
  • Vous pouvez définir plusieurs risques d'exceptions sur une même méthode. Il suffit de séparer les déclarations par une virgule !
  • Dans cette méthode, vous pouvez définir les conditions d'instanciation d'une exception et lancer cette dernière grâce au mot clé throw suivie de l'instanciation.
  • Une instanciation lancée par le biais de l'instruction throw DOIT ÊTRE DÉCLARÉE avec throws au préalable ! !
Je vous propose maintenant de voir les collections !

Faites tout de même une pause pour bien comprendre le fonctionnement des exceptions ; pour les avides de connaissances, rendez-vous au chapitre suivant !


0 comments to "LECON 206"

Post a Comment

About This Blog

Aller au debut de la page