Chapitre 5 : Contrôle du flux, exceptions et assertions

Le but de cet article est de présenter de façon précise et concise les notions du langage Java relatives au contrôle de flux, aux exceptions et aux assertions. En clair, nous parlerons des structures conditionnelles (if-else, switch…), des boucles (while, for…), de la gestion des exceptions et enfin de l'utilisation des assertions.

2 commentaires Donner une note à l'article (5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Plusieurs parmi nous (je suis le premier) ont appris le langage Java en rassemblant des bouts de code dans le but de réaliser un logiciel (ce que l'on a coutume d'appeler « formation sur le tas »). Quoique cela a toujours fini par donner un résultat, on ne saurait cependant renier son inconvénient majeur : à la fin on ne sait jamais comment les choses fonctionnent au fond. Ce qui peut avoir pour conséquences : la mauvaise utilisation des types d'objets et de la mémoire qui parfois occasionne des pertes en temps énormes suite à une mauvaise approche lors du débogage.

À la suite du quatrième chapitre qui concernait les opérateurs en Java, ce chapitre (qui est le cinquième de la suite le mémo du certifier Java SE 6 dans lequel l'on retrouve les chapitres suivants : Déclaration et contrôle d'accès, La programmation orientée objet, Les opérateurs) fera le point sur les instructions de contrôle de flux en Java (if-else, switch, while…), la gestion des exceptions et l'utilisation des assertions. À l'issue de cette présentation, cinq exercices corrigés sont mis à la disposition du lecteur pour des besoins d'évaluation personnelle. Je conseille vivement que ce test soit effectué avant et après la lecture de l'article.

II. Les structures conditionnelles

On entend par « structures conditionnelles » des instructions qui permettent d'exécuter un programme en examinant différents cas.

II-A. La structure if-else

Elle se traduit en français par le si-sinon et permet d'exécuter une ou plusieurs instructions selon que la condition de test est vraie ou fausse. Dans le cas d'une structure if-else, la condition de test peut être n'importe quelle instruction qui résulte à une valeur booléenne.

  • Le format basique d'une instruction avec if est le suivant :
     
    Sélectionnez
    if(expression-booléenne){
        //fait quelque chose si l'expression booléenne est à true
    }
  • Le format d'une instruction avec if-else est le suivant (le bloc else est optionnel) :
     
    Sélectionnez
    if(expression-booléenne){
         //fait quelque chose si l'expression booléenne est à true
    } else {
         //fait quelque chose si l'expression booléenne est à false 
    }
  • Les accolades ne sont pas obligatoires lorsque vous souhaitez gérer une seule instruction dans un bloc if ou else.
  • Règles d'utilisation des blocs else et else if :
  • Toute expression qui donne lieu à une valeur booléenne peut être utilisée comme condition de test avec le if :
  1. On peut avoir zéro ou un bloc else pour un if donné ;
  2. On peut avoir zéro ou plusieurs else ifs pour un if donné ;
  3. Une fois qu'une condition est vérifiée, tous les autres blocs sont ignorés.
     
    Sélectionnez
    if((x > 3) && (y < 2) | doStuff()){}
    boolean doStuff(){}
  4. Seules les expressions booléennes sont utilisées comme condition de test pour le if :
     
    Sélectionnez
    int trueInt = 1; if(trueInt) //illégal

II-B. La structure switch

Elle est très utile lorsque vous voulez gérer plusieurs blocs avec des if imbriqués. Afin de contourner la lourdeur des if/else-if/if, la structure switch propose d'énumérer la liste des cas possibles dans une syntaxe assez claire et intelligible.

  • Exemple de transformation d'une structure de contrôle if/else en bloc switch :
     
    Sélectionnez
    int x = 3;
    if(x == 1){
      //fait quelque chose
    } else if(x == 2){
       //fait quelque chose
    }else { //valeur par défaut
       //fait quelque chose
    }
     
    Sélectionnez
    int x = 3;
    switch(x){
       case 1 :
          break;
       case 2 :
          break;
       default :            
    }
  • Le mot clé break est optionnel et permet de sortir de la structure conditionnelle. Son inclusion ou exclusion cause de grands changements dans l'exécution d'un switch.
  • Le mot clé default permet de spécifier l'instruction ou le bloc de code qui devra être exécuté s'il n'existe pas de case correspondant à la valeur de l'expression de test. Il peut être placé en début, au milieu ou à la fin d'une structure switch.
  • La forme générale d'une instruction switch est la suivante :
     
    Sélectionnez
    switch(expression) {
      case constante 1 : bloc de code à exécuter « si&#160;expression = constante 1&#160;»;
      case constante 2 : bloc de code à exécuter « si&#160;expression = constante 2&#160;»;
      default : bloc de code à exécuter par défaut ;
    }
  • L'expression de test pour une structure switch doit être de type char, byte, short, int ou enum (dans la version 7 de Java SE, il est possible d'utiliser le type String comme type de l'expression de test). Ce qui signifie que, à défaut d'utiliser une expression de type enum, seules les variables qui peuvent être automatiquement transformées en int sont admises. Dans le cas contraire (si vous utilisez une expression de type long, float, double, boolean) vous aurez une erreur de compilation.
  • On peut utiliser un wrapper comme expression de test. Mais cela n'est valable que pour les wrappers correspondant aux types primitifs admis, à savoir : Character, Byte, Short et Integer :
     
    Sélectionnez
    switch(new Integer(4)) {
        case 4 : 
    }
  • La constante d'un case doit être de même type que l'expression de test. De plus, cette constante doit être clairement établie à la compilation. Ce qui signifie qu'on peut seulement utiliser des constantes ou des variables final auxquelles on a affecté une valeur littérale. Il ne suffit pas que la variable soit final, elle doit aussi avoir une valeur à sa création :
     
    Sélectionnez
    final int a = 1;
    finale in b;//pas de valeur à la compilation
    b = 2;//on affecte la valeur à l'exécution
    int x = 0;
    switch(x){
        case a: //ok
        case 3: //ok
        case b: //erreur de compilation 
        case new Integer(5) : //compile error
    }
  • La structure switch teste uniquement l'égalité.
  • Il peut se poser un problème lorsqu'on utilise des variables de taille inférieure au type int :
     
    Sélectionnez
    byte g = 2;
    switch(g) {
       case 23:
       case 128://erreur de compilation, car 128 est très grand pour le type byte 
    }
  • Il n'est pas autorisé d'avoir plus d'un case avec la même valeur de test, cela conduit à une erreur de compilation :
     
    Sélectionnez
    int temp = 90;
    switch(temp) {
        case 80 :
        case 80 : //erreur de compilation
        case 90 :
    }
  • Les instructions case sont évaluées du premier case (après le mot clé switch) au dernier case du bloc. Et le premier case qui correspond à l'expression du switch est le point d'entrée de l'exécution de la structure.
  • À la rencontre d'un mot clé break, le programme sort du switch pour exécuter l'instruction qui le suit. Si aucun break n'est rencontré, le programme va exécuter tous les case restants jusqu'à ce qu'il rencontre un break oujusqu'à ce qu'il atteigne la fin du bloc switch :
     
    Sélectionnez
    int x = 1;
    switch(x) {
        case 1 : System.out.print("1 ");
        case 2 : System.out.print("2 ");
        case 3 : System.out.print("3 ");
    }
  • Le résultat de ce programme sera : 1 2 3 pour la simple raison qu'aucune instruction break n'a été rencontrée.
  • Le cas par défaut (default) fonctionne comme les case.

III. Les boucles

Encore appelées instructions répétitives, les boucles sont des structures qui permettent d'exécuter plusieurs fois un bloc d'instructions jusqu'à ce qu'une condition ne soit plus vérifiée. On les traduit généralement en français par : tant quefaire { … }.

III-A. La boucle while

  • Les boucles avec while sont indiquées pour des scénarios où l'on n'a pas une idée du nombre de fois qu'un bloc ou une instruction devrait être exécuté.
  • Le format d'une boucle while est le suivant :
     
    Sélectionnez
    while(expression-booléenne){
    }
  • Une boucle while est exécutée, si et seulement si l'expression de test est à true.
  • L'expression de test d'une boucle while doit être déclarée avant son utilisation :
     
    Sélectionnez
    while(boolean b = true){//erreur de compilation
    }
  • Pour l'exécution d'une seule instruction, on peut se passer des accolades :
     
    Sélectionnez
    int x = 0, y = 10;
    while (++x < y)
       System.out.println("nombre de fois : " +x +" fois !!!");
  • Exemple de boucle while :
     
    Sélectionnez
    boolean b = true;
    int i = 0;
    while(b){
       if(i == 5){
          b = false;
       }  
       System.out.println("i : "+ i++);
    }

III-B. La boucle do…while

  • C'est une variante de la boucle while. L'une des particularités de cette boucle vient du fait que la condition de test est vérifiée après l'exécution du bloc placé dans la boucle. Ceci dit, une boucle do…while sera toujours exécutée au moins une fois.
  • De plus, une boucle do…while se termine par un point-virgule « ; ».
  • Le format d'une boucle do…while est le suivant :
     
    Sélectionnez
    do {
    }while(booleanExpression);//Se rassurer qu'il y a le &#8220;;&#8221; après le while
  • De même que pour la boucle while, on peut se passer des accolades pour l'exécution d'une seule instruction. Cependant, si l'on met plus d'une instruction entre le do et le while sans accolades, une erreur va se produire à la compilation :
     
    Sélectionnez
    int x = 0, y = 10;
    do
       System.out.println("nombre de fois : " +x +" fois !!!");
    while (++x < y);

III-C. La boucle for

  • La boucle for est vraiment utile lorsque le nombre d'itérations est connu.
  • La déclaration d'une boucle for contient trois parties :
  • Les trois parties sont séparées par un point-virgule (« ; ») et sont indépendantes les unes des autres :
     
    Sélectionnez
    for(/*Initialisation*/; /*condition*/; /*iteration*/){}
     => for(int i = 0; i<5; i++){}
  • Initialisation
     
    Sélectionnez
    for(int x = 0, y = 3; y > 3; y++){}
  • L'utilisation d'une variable déclarée dans une boucle for est limitée au bloc d'instructions défini dans la boucle :
 
Sélectionnez
for(int x = 1; x < 2; x++){
    System.out.println(" x: "+x);//legal
}
System.out.println(" x:"+x);//Illégal, impossible d'accéder à la variable x hors de la boucle
  • Condition
 
Sélectionnez
for(int x = 0; ((((x < 10) && (y-- > 2)) | x == 3)); x++){} //c'est légal
for(int x = 0; (x > 5), (y < 2); x++){} //Illégal, plusieurs expressions ont été déclarées
  • Itération
     
    Sélectionnez
    for(int i = 0; i < 4; 
       System.out.println(" first print "), System.out.println(" second print")){}
  • L'expression d'itération est exécutée après chaque exécution du contenu de la boucle :
     
    Sélectionnez
    for(int x = 0; x < 1; x++){//contenu de la boucle
       System.out.println(" first print "), System.out.println(" second print")){} }
  • En cas d'absence des instructions qui forcent la sortie d'un programme (break, return, Exception, System.exit()), l'évaluation de l'expression d'itération et l'évaluation de la condition de test sont toujours les dernières instructions exécutées dans une boucle for.
     
    Sélectionnez
    for(;;){}//elle va se comporter comme une boucle infinie 
    for(; i < 10; ){}//elle va se comporter comme une boucle while
  • Les trois parties (initialisation, condition, itération) d'une boucle for sont indépendantes les unes des autres. Elles n'ont donc pas besoin d'avoir systématiquement les mêmes variables :
 
Sélectionnez
int b = 3; 
for(int a = 1; b != 1; System.out.println("Iterate")){ b = b - a;}

III-D. La boucle for-each

  • Elle a été introduite dans la version 6 du JDK et permet de simplifier le parcours des tableaux et des collections.
  • À la différence d'une simple boucle for qui a trois parties, le for-each n'en a que deux :
     
    Sélectionnez
    int [] a = {1,2,3}; 
    for(int n : a){
      System.println("value : "+n);
    }
  • Le format d'un for-each est le suivant : for(déclaration : expression).
  • La déclaration doit être de type compatible avec les éléments du tableau ou de la collection.
  • L'expression doit pourvoir être évaluée à un(e) tableau(collection). Elle peut donc être un(e) tableau(collection) ou une méthode qui retourne un(e) tableau(collection) :
 
Sélectionnez
for(Object obj : listOf()){//la méthode retourne une liste d'objets
   System.out.println(obj);
}
List listOf(){}

IV. Les instructions break et continue

Ce sont des instructions Java qui permettent d'effectuer des sauts dans un programme à la façon du goto en Visual Basic.

  • Le mot clé break est utilisé pour sortir d'une boucle ou d'un bloc switch. Il permet d'arrêter l'exécution d'une boucle pour passer à l'instruction qui suit la boucle :
     
    Sélectionnez
    int x = 0;
    for(int i = 0; i < 5; i++){
       if(i == 1)
          break;
       x = i;       
       System.out.print(x+" ");      
    }
    System.out.print(x+" ");
    //le résultat sera :  0  0
  • Le mot clé continue est utilisé à l'intérieur d'une boucle pour passer à l'itération suivante :
     
    Sélectionnez
    int x = 0;
    for(int i = 0; i < 5; i++){
       if(i == 1)
          continue;
       x = i;       
       System.out.print(x+" ");      
    }
    System.out.print(x+" ");
    //le résultat sera :  0  2  3  4  4
  • L'instruction continue permet juste de mettre fin à l'itération courante pour passer à la suivante dans la même boucle :
 
Sélectionnez
int i;
for(i =0; i < 20; i++){        
  if(i == 1){
    System.out.print(", continue-i : "+i);
    continue;
  } else if(i == 2){
     System.out.print(", break-i : "+i);
     break;
  }
  System.out.print("i : "+i);
}
System.out.print(",out-i : "+i);
/*La solution : i : 0, continue-i : 1 (on saute l'itération sans imprimer i : 1), break-i&#160;: 2 (on sort de la boucle sans incrémenter i), out-i&#160;: 2 */

IV-A. Les étiquettes

On les utilise très souvent pour les boucles for ou while en conjonction avec les instructions break ou continue pour définir à quel niveau devra s'appliquer le break ou le continue.

  • Une étiquette doit être placée juste avant la boucle à étiqueter suivie des deux-points (« : ») :
     
    Sélectionnez
    foo:
    for(int x = 3; x<20;x++){
        while(y > 7){ y--; break foo;}
    }
  • L'utilisation des étiquettes sur une boucle est nécessaire uniquement dans les situations où nous avons des boucles imbriquées et que nous voulons indiquer la boucle sur laquelle le break ou le continue doit être appliqué.
  • Dans l'exemple suivant, l'instruction break va faire sortir le programme de la boucle étiquetée (la boucle for) et non de la boucle qui lui est directement proche (boucle while) :
     
    Sélectionnez
    foo:
    for(int x = 3; x<20;x++){
        while(y > 7){ y--; break foo;}
    }
  • Une étiquette doit respecter les critères d'une variable Java valide (voir chapitre 1).
  • Pour utiliser une étiquette avec un break ou un continue, on fait simplement suivre l'instruction break ou continue de l'étiquette puis on met un point-virgule :
 
Sélectionnez
outer:
for(;;){
    while(true){
        System.out.print("while  ");
        break outer;
    }
    System.out.print("for  ");//ne sera pas affiché
}
System.out.print("out  ");
Ce code produit : while out
     
outer:
for(int i = 0; i < 2; i++){
    for(;;){
        System.out.print("for 1 ");
        continue outer;
    }
    System.out.print("for 2");
}
System.out.print("out");
Ce code produit: for 1 for 1 out

V. Exception

Les exceptions représentent un mécanisme Java permettant de séparer le code fonctionnel du traitement des erreurs qui peuvent survenir. Il se compose d'un ensemble de classes qui définissent le type d'erreur et de mots clés permettant de détecter, traiter, propager ou lister les exceptions.

V-A. Gestion des exceptions

V-A-1. Gestion des exceptions à l'aide du bloc try-catch

  • Le mot clef try est utilisé pour définir un bloc de code dans lequel une exception peut être levée :
     
    Sélectionnez
    try{
       //opération_risquée_1
       //opération_risquée_2
       //opération_risquée_3
    }catch(MyException2 m){
       //Traitements_des_exceptions_1
    }catch(Exception2 m){
       //Traitements_des_exceptions_2
    }
  • Si une exception survient dans le bloc try, les opérations qui viennent après la ligne de l'exception ne seront plus exécutées. Et la suite de l'exécution du programme va se poursuivre dans le bloc catch correspondant à l'exception levée. En d'autres termes, si une exception de type MyException2 est levée à l'opération risquée 1, les opérations risquées 1 et 2 ne seront plus exécutées et la suite du programme va se poursuivre dans le bloc catch de l'exception MyException2.
  • Le bloc catch suit immédiatement le bloc try.
  • On peut avoir plusieurs blocs catch pour un bloc try dans ce cas, l'ordre d'apparition des blocs catch compte.

V-A-2. Utilisation du mot clef finally

  • Le bloc finally permet de définir un ensemble d'instructions qui seront toujours exécutées après le bloc try qu'il y ait exception ou pas.
  • Même s'il y a une instruction return dans le bloc try, le bloc finally sera exécuté juste avant que l'instruction return ne soit exécutée.
  • C'est l'endroit idéal pour fermer les fichiers et libérer les ressources utilisées par l'application.
  • Si une exception est levée, le bloc finally est exécuté immédiatement après le bloc catch correspondant.
  • Le bloc finally n'est pas obligatoire.
  • Il est possible de faire suivre directement le bloc try par un bloc finally :
     
    Sélectionnez
    //Le code suivant donne un exemple de bloc try suivi de finally 
    try {}finally{}
  • Il est impossible d'utiliser le bloc try sans bloc catch ou finally.
  • Il n'est pas possible de mettre une instruction entre les blocs try-catch, try-finally et catch-finally :
     
    Sélectionnez
    try{
    }
    System.out.print("ok");//erreur de compilation
    catch(Exception e){}
  • Un bloc finally doit immédiatement suivre le dernier bloc catch ou le bloc try en cas d'absence du bloc catch.
  • Il n'est pas possible de mettre du code entre un bloc try et un bloc catch ou finally,cela produirait une erreur lors de la compilation.

V-A-3. Propagation des exceptions non traitées

  • Une exception non traitée peut causer l'arrêt de l'application.

V-B. Traitement des exceptions

V-B-1. Hiérarchie des exceptions

Image non disponible
  • Les classes qui dérivent de la classe Error permettent de présenter des situations qui ne devraient pas apparaître durant l'exécution normale d'une application (par exemple le manque de mémoire).
  • Il n'est pas obligatoire de gérer les exceptions de type Error :
     
    Sélectionnez
    //Exception de type Error, pas de problème
    void faireQuelquechose(){
       
     throw new AssertionError();
    }
    
    //Exception de type RuntimeException, pas de problème
    void faireQuelquechose(){
       throw new NullPointerException();
    }
    
    // Problème, l'exception doit être soit entourée d'un bloc try-catch soit   // listée au niveau de la méthode à l'aide du mot clé throws
    void faireQuelquechose(){//erreur lors de la compilation
       throw new Exception();
    }
  • Techniquement, une erreur n'est pas une exception pour la simple raison qu'une erreur hérite de la classe java.lang.Error tandis qu'une exception hérite de la classe java.lang.Exception.
  • La classe Throwable met à la disposition des sous-classes, un ensemble de méthodes utiles pour la gestion des exceptions. Exemple : la méthode printStackTrace().
  • Les exceptions de type Exception, Error, RuntimeException et Throwable peuvent être levées à l'aide du mot clé throw et peuvent bien être gérées dans un bloc catch.

V-B-2. Gestion des groupes d' exceptions

  • Il est possible de gérer plusieurs types d'exception dans un seul bloc catch.
  • Si l'exception spécifiée dans le bloc catch n'a pas de sous-classes alors, seule l'exception spécifiée pourra être traitée dans le bloc.
  • Si l'exception spécifiée dans le bloc catch possède plusieurs sous-classes, toute exception issue d'une sous-classe de la classe spécifiée pourra être gérée dans ce bloc :
 
Sélectionnez
//le bloc catch va gérer toute exception qui hérite directement ou indirectement de la classe Exception (Ex&#160;: FileNotFoundException, IOExeption, NullPointerException,&#8230;).
try{}catch(Exception ex){}

V-B-3. La déclaration des blocs catch

  • Lorsqu'une exception est levée, la JVM essaie de chercher un bloc catch correspondant à l'exception. Si aucun bloc ne correspond, l'exception est propagée.
  • Si l'on a un bloc try avec plusieurs blocs catch, le catch avec l'exception la plus spécifique devra toujours être placée au-dessus de celles qui sont générales (super-classe) :
     
    Sélectionnez
    //le code suivant ne peut pas être compilée pour la simple raison que l'IOException est une super-classe de la classe FileNotFoundException. Il faut inverser l'ordre pour faire compiler le programme
    try{}catch(IOException e){}catch(FileNotFoundException e){}
  • Si une exception n'est ni une sous-classe ni une super-classe, alors sa position dans l'ensemble des blocs catch n'a pas d'importance.

V-C. Déclaration des exceptions

  • L'ensemble des exceptions qu'une méthode peut générer doit être déclaré ou géré (à moins que l'exception soit une sous-classe de la classe RuntimeException ou Error).
  • Le mot clé throws permet de déclarer la liste des exceptions qu'une méthode peut générer :
     
    Sélectionnez
    void myFunction() throws MyException1, MyException2 {}
  • Toute méthode qui peut générer une exception doit déclarer l'exception. À moins que l'exception soit une sous-classe de RuntimeException.
  • Chaque méthode doit, soit gérer (à l'aide d'un bloc catch), soit déclarer (à l'aide du mot clé throws) toute exception de type checked exception. RuntimeException et Error et les sous-classes associées sont des exceptions de type unchecked exception.
  • Les exceptions de type unchecked exceptions n'ont pas besoin d'être gérées ou déclarées.
  • Un checked exception doit obligatoirement être géré quelque part dans le code, dans le cas contraire le code ne pourra pas être compilé.
  • Pour créer une exception personnalisée il suffit d'hériter de la classe Exception ou d'une de ses sous-classes :
     
    Sélectionnez
    class MyException extends Exception{}
  • Lorsqu'une exception est propagée dans un bloc try ou catch, s'il existe un bloc finally, les instructions de ce bloc seront d'abord exécutées avant que le contrôle ne soit passé à la méthode appelante.
  • Si vous propagez un checked exception, vous devez, soit le déclarer, soit le traiter :
 
Sélectionnez
//It is illegal, you must declare the throw exception
void  doStuff(){try{}catch(Exception e){ throw e;}}

V-D. Quelques exceptions et erreurs communes

Runtime exception Checked Exception Error
ArithmeticException ClassNotFoundException AssertionError
ArrayIndexOutOfBoundsException FileNotFoundException ExceptionInInitializerError
ClassCastException IOException NoClassDefFoundError
IllegalArgumentException NoSuchMethodException OutOfMemoryError
IllegalStateException ParseException StackOverflowError
NullPointerException    
NumberFormatException    

VI. Assertion

D'après le dictionnaire, une assertion est une proposition donnée et soutenue comme vraie (une affirmation). Appliquée à Java, c'est un mécanisme qui permet au programmeur de vérifier la validité de certaines conditions. En d'autres termes, les assertions donnent la possibilité aux développeurs de s'assurer qu'une variable a effectivement l'état qu'elle devrait avoir.

VI-A. Aperçu général des assertions

  • Dans la pratique, les assertions permettent de mettre en œuvre la programmation par contrat.
  • La programmation par contrat est une technique de programmation dans laquelle les interactions entre les objets sont régies par des règles. Elle consiste à indiquer au niveau des classes d'objets les invariants (conditions qui doivent être vérifiées tout au long de la vie d'un objet) et au niveau des méthodes, des préconditions (contraintes à respecter à l'appel d'une méthode) et postconditions (contraintes à respecter après l'exécution d'une méthode).
  • Pour matérialiser le concept de programmation par contrat, considérons l'exemple (purement simpliste) d'une classe qui remplit un tableau de racines carrées :
     
    Sélectionnez
    public class RacineCarree {
       private double[] racine = new double[20]; // invariant : racine  != null
       
       public RacineCarree(){
          assert(racine != null);//on s'assure que l'invariant est toujours vrai
       }
       
       public double[] fillRacine(){
          double[] values = new double[racine.length];
          for(int i = 0; i < racine.length; i++){
             values[i] = sqrt(racine[i]);
           }
          
          return values;
       }
       
       private double racineCarree(double value){
          assert(value >= 0);//pré-condition
    
           double resultat = sqrt(value);
    
           assert(resultat == sqrt(value));//post condition 
          
          return resultat;
       }
    }
  • Les codes d'assertion sont ignorés au déploiement de l'application.
  • On fait toujours une assertion pour savoir si une hypothèse est vraie. Si c'est le cas alors pas de problème. Mais dans le cas contraire (si l'hypothèse n'est pas vérifiée), l'erreur AssertionError sera propagée.
  • Il existe deux types de test d'assertion :
     
    Sélectionnez
     private void doStuff(){ assert(y > x);}
  • Simple, en plus de l'expression booléenne, ce type d'assertion comporte une seconde expression qui sera associée à l'erreur pour apporter plus de détails. Les deux expressions sont séparées par les deux points (« : ») :
 
Sélectionnez
private void doStuff(){assert(y > x) : "Y is " + y + " x is " + x;}
  • Les deux types d'assertions propagent une erreur de type AssertionError. Mais la version simple permet d'apporter plus de détails pour faciliter le débogage.
  • Les codes d'assertions sont activés pendant les tests ou le débogage, mais ils sont désactivés (ignorés par la JVM) lorsque l'application est déployée.

VI-B. Règles d'utilisation des assertions

  • Une assertion peut avoir une ou deux expressions selon que vous utilisez le type « simple » ou le « type très simple ».
  • La première expression d'une instruction assert doit toujours être équivalente à une valeur booléenne.
  • La seconde expression est utilisée dans le cas d'une assertion de type simple, elle peut être toute expression qui résulte à une valeur. Elle est utilisée pour générer un message qui apporte plus de détails en vue de faciliter le débogage.
  • Exemples d'expressions légales et illégales :
     
    Sélectionnez
    int aReturn(){return 1;}
    int x = 1;
    boolean b = true;
    //legal expression
    assert(x==1);
    assert(b);
    assert true;
    assert(x == 1) : x;
    assert(x == 1) : aReturn();
    //illegal expression
    assert(x = 1);//none are boolean
    assert(x);
    asser(0);
    assert(x==1) : ;// none of these return a value
    assert(x==1); : noReturn();
    assert(x==1) : validdAssert va;int aReturn(){return 1;}
    int x = 1;
    boolean b = true;
    //legal expression
    assert(x==1);
    assert(b);
    assert true;
    assert(x == 1) : x;
    assert(x == 1) : aReturn();
    //illegal expression
    assert(x = 1);//none are boolean
    assert(x);
    asser(0);
    assert(x==1) : ;// none of these return a value
    assert(x==1); : noReturn();
    assert(x==1) : validdAssert va;

VI-C. Activation des assertions

  • Avant Java 1.4, le mot assert pouvait être utilisé comme un identifiant en Java. Mais depuis la version Java 1.4, assert est devenu un mot clé.
  • Le compilateur Java 6 considère le mot assert comme un mot clé. Une erreur est générée si assert est utilisé comme un identifiant.
  • Si vous utilisez assert comme identifiant dans le code, vous devez le compiler en utilisant l'option : « - source 1.3 ».
  • assert peut uniquement être utilisé comme identifiant dans Java 1.3, dans les versions supérieures la compilation échoue.
  • assert ne peut pas être utilisé comme un mot clé dans la version Java 1.3.
  • Une fois que les assertions ont été placées dans le code, vous pouvez les activer ou les désactiver à la compilation. Par défaut, elles sont désactivées.
  • Vous pouvez activer les assertions à la compilation comme suit :
     
    Sélectionnez
    java -ea TestCass 
    or
    java -enableassertions TestClass
  • Vous pouvez désactiver les assertions à la compilation comme suit :
     
    Sélectionnez
    java -da TestClass 
    or
    java -disableassertions TestClass. 
    //Ne pas oublier que les assertions sont désactivées par défaut
  • Les lignes de commandes pour les assertions peuvent être utilisées de plusieurs façons :
  • Avec un nom de classe pour activer ou désactiver les assertions dans une classe spécifique.
  • Vous pouvez désactiver les assertions dans une seule classes mais, vous pouvez les activer pour toutes les autres classes comme suit :
     
    Sélectionnez
    java -ea -da:com.TestClass . 
    That means, enable assertions in general, but disable them in com.TestClass
  • Vous pouvez faire de même pour un package comme suit :
     
    Sélectionnez
    java -ea -da:com...  . 
    //That means, enable assertions in general but disable 
    //them in the package com and all of subpackages
  • Vous pouvez activer les assertions dans toutes les classes sauf dans les classes système comme suit :
     
    Sélectionnez
    java -ea -dsa

VI-D. Utilisation appropriée des assertions

  • Il n'est pas conseillé (même si c'est possible) de gérer les erreurs d'assertion (AssertionErrors).
  • AssertionError est une sous-classe de la classe Throwable, donc elle peut être gérée dans un bloc try-catch. Mais ce n'est pas conseillé.
  • Il est inapproprié d'utiliser les assertions pour valider les arguments des méthodes publiques. L'instruction suivante est inappropriée :
     
    Sélectionnez
     public void doStuff(int x){ assert ( x > 0);}
  • Si vous voulez valider les arguments d'une méthode publique, vous devez probablement utiliser les exceptions telles que IllegalArgumentException ou NumberFormatException.
  • Il est approprié d'utiliser les assertions pour valider les arguments des méthodes privées.
  • Il est approprié d'utiliser les assertions dans les méthodes publiques pour tester un cas dont on est sûr qu'il ne se produira jamais :
     
    Sélectionnez
    switch(x) {
        case 1: y = 3; break;
        case 2: y = 9; break;
        default : assert false;
    }
  • Il est inapproprié de modifier les variables dans une assertion :
     
    Sélectionnez
    public void doStuff(){ assert(modifyThing()); }
    public boolean modifyThing(){y = x++; return true;}
Ligne de commande Signification
java -ea ou java -enableassertions Activation des assertions
java -da ou java -disableassertions Désactivation des assertions (état par défaut)
java -ea:com.company.MyClass Activation des assertions dans Myclass du package com.company
java -ea:com.company… Activation des assertions dans le package com.company et ses sous-packages
java -ea -dsa Activation des assertions en général à l'exception des classes système
java -ea -da:com.company… Activation des assertions en général sauf dans le package com.company et ses sous-packages

VII. Exercices

VII-A. Corrigez les erreurs et trouvez le résultat

 
Sélectionnez
class Boucle {
    public static void main(String[] args) {
      int[] x = {1,2,3,4,5,};
      int y = 0; 
      for(y : x) {
        System.out.print(y + " ");
      }
    }
  }

VII-B. Corrigez les erreurs et trouvez le résultat

 
Sélectionnez
class ThrowException {
  String s = "-";
  public static void main(String[] args) {
    try {
      throw new Exception();
    } catch (Exception e) {
        try { 
          try { throw new Exception();
          } catch (Exception ex) { s += "A "; }
          throw new Exception(); } 
        catch (Exception x) { s += "B "; }
        finally { s += "C "; }
    } finally { s += "D "; }
    System.out.println(s);
} }

VII-C. Corrigez les erreurs et trouvez le résultat

 
Sélectionnez
public class SwitchCase {
    static int x = 1;
    final static int z = 2;
    static int p = 6;
    public static void main(String... args) {
      String s = "";      
      for(int y = 0; y < 3; y++) {
        x++;
        switch(x) {
          case z: s += "2 ";
          case 3: s += "3 ";
          case new Integer(4): { s+= "4 "; break; }
          default: s += "d ";
          case 5: s+= "5 ";   
          case p: s+= "5 ";           }
      }
      System.out.println(s);   
    }
    static { x++; }
  }

VII-D. Corrigez les erreurs et trouvez le résultat

 
Sélectionnez
public class Statement {
   public static void main(String... args) {
      int[] ia = {1,3,5,7,9};
      for(int x : ia) {
        for(int j = 0; j < 3; j++) {
          if(x > 4 && x < 8) continue;
          System.out.print(" " + x);
          if(j == 1) break;
          continue;
        }
        continue;
      }
    }
  }

VII-E. Corrigez les erreurs et trouvez le résultat

 
Sélectionnez
public class CatchException {
    static String s = "";
    public static void main(String[] args) {
      try {
        throw new Exception();
      s += "1";
      } catch (Exception e) { s += "2";
     } catch (NullPointerException e) { s += "3";}
      finally { s += "4"; s += "5";}

        System.out.println("s = "+s);
      }
 }

VIII. Correction des exercices

VIII-A. Exercice 1

  1. Erreurs
    Dans une boucle for-each, il faut déclarer la variable qui devra contenir chaque objet à l'intérieur de l'instruction. Donc il faudra remplacer for(: x) par for(int y : x). Puis supprimer/commenter la déclaration de la variable y avant l'instruction forafin d'éviter les conflits de noms entre les deux variables locales.
  2. Solution
    Le résultat est : 1 2 3 4 5.

VIII-B. Exercice 2

  1. Erreurs

    Une variable non-statique ne peut pas être directement employée dans une méthode statique. Vous devez donc rendre la variable « s » statique : static String s = "-";.

  2. Solution

Le résultat est : -A B C D.

VIII-C. Exercice 3

  1. Erreurs

    L'expression de test pour une structure switch doit être une constante de type char, byte, short, int ou enum. Et cette constante doit être clairement établie à la compilation. Pour cela, vous allez remplacer new Integer(4) par 4 et faire précéder la déclaration de la variable p de final (final static int p = 6;).

  2. Solution

Le résultat est : 3 4 4 5 5 .

Lorsque y = 0, x = 3. Étant donné qu'il n'y a pas de break entre case 3 et case 4, le programme ajoutera 3 4 à s. Lorsque y = 1, x = 4. Le programme ajoute 4 à s et sort du switch à cause du break. Lorsque y = 2, x = 5. Le programme exécute case 5 puis case (p = 6) et ajoute à s 5 5 car, aucun break ne les sépare. Le tout donne 3 4 4 5 5.

VIII-D. Exercice 4

  1. Erreurs

    Il n'y a aucune erreur.

  2. Solution

Le résultat est : 1 1 3 3 9 9. À chaque fois que j = 1, le programme sort de la seconde boucle et à chaque fois que x vaut 5,6 ou 7, le programme passe à la valeur suivante de j sans imprimer x.

VIII-E. Exercice 5

  1. Erreurs

    Il n'est pas possible de mettre des traitements directement à la suite des in throw et return. Cela produit une erreur de compilation, car ils ne seront jamais exécutés. IL faudra donc mettre l'instruction s += "1"; avant l'instruction throw.

    Si l'on a un bloc try avec plusieurs blocs catch, le catch avec l'exception la plus spécifique devra toujours être placée au-dessus de celles qui sont générales (super-classe). Il faudra donc permuter les blocs catch, car NullPointerException hérite de Exception.

  2. Solution

La solution est : s = 1245.

IX. Conclusion

Le prochain article va s'appesantir sur le formatage des E/S et l'analyse syntaxique. L'on parlera entre autres : de la manipulation des chaînes de caractères, du formatage, de la sérialisation et de l'analyse syntaxique. Avant de nous quitter, je souhaite adresser mes remerciements à f-leb pour la relecture orthographique, à thierryler pour ses propositions et Mickael Baron pour ses propositions, ses encouragements et le temps consacré aux diverses relectures.

Pour réaliser cet article, j'ai fait usage de deux livres que je trouve complémentaires et que je n'hésite pas à vous conseiller. Il s'agit de :

  • SCJP Sun Certified Programmer for Java 6 Study Guide de Kathy Sierra et Bert Bates
  • A Programmer's Guide to Java™ SCJP Certification Third Edition de Khalid A. Mughal et Rolf W. Rasmussen

Suivez moi sur twitter

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2013 naf87. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.