5. Blocs, boucles et contrôles

5.1. Blocs

Par définition, un bloc est un ensemble de commandes, d'instructions et de déclarations compris entre deux accolades. Un bloc peut se trouver dans une classe, en tant que membre (cas des blocs statiques et non statiques, que nous avons déjà vus), ou dans une méthode. Il peut également se trouver après une instruction if (par exemple), et aura alors une fonction particulière. On peut définir des blocs dans des blocs. Il y a deux points importants à retenir au sujet des blocs. Tout d'abord, un bloc définit la portée d'une variable. Une variable définie à l'intérieur d'un bloc n'est pas connue à l'extérieur de ce bloc. Toute variable définie dans un élément for ou while est considérée comme faisant partie du bloc qui suit ce for ou ce while. La seconde chose est qu'une variable définie dans un bloc ne peut pas avoir le même nom qu'une autre variable définie dans un bloc englobant. Ce genre de chose génère une erreur de compilation. Cette règle s'applique également pour les paramètres d'une méthode : il n'est pas possible de définir, dans une méthode, une variable qui aurait même nom que l'un des paramètres de cette méthode. Voyons ceci sur quelques exemples.

Exemple 53. Exemples de blocs

public  class Marin {  
     private String nom ;  
      
     static {  
         // ceci est un bloc statique  
    }  
      
    {  
         // ceci est un bloc non statique  
    }  
      
     public  void setNom(String nom) {  
        String nom ;  // ERREUR !!! impossible de définir une variable 
                      // portant le nom d'un paramètre  
         this.nom = nom ;  
          
        {  
             // nous sommes dans un bloc dans la méthode setNom(String)  
             int i  ;  
            {  
                 // nous sommes dans un sous-bloc de ce bloc  
                 int i =  0 ;  // ERREUR !!! il existe une variable i 
                             // dans le bloc englobant  
            }  
        }  
          
        i =  0 ;  // ERREUR !!! i n'est pas connue, nous ne sommes pas 
                 // dans son bloc de définition  
    }  
      
     public  void augmenteSalaire(int montant) {  
          
         for (int i =  0 ; i <  10 : i++) {  
             // bloc de la boucle for, i est définie dans ce bloc  
             int i =  0 ;  // ERREUR !!! impossible de définir i, qui existe déjà  
        }  
          
        System.out.println("i = " + i) ;  // ERREUR !!! i n'est pas connue, 
                                          // nous sommes sorti de la boucle for  
    }  
}

D'une façon générale, il faut toujours tenter de restreindre le plus possible la portée d'une variable. Définir l'index d'une boucle for à l'extérieur de la boucle ne constitue par une erreur du point de vue du langage, mais est une très mauvaise habitude de programmation.

5.2. Mots-clés réservés

Il existe une cinquantaine de mots réservés dans le langage Java. La liste de ces mots n'a pratiquement pas changé depuis la création du langage.
  • 1.2 : création de stricfp ;
  • 1.4 : création de assert ;
  • 1.5 : création de enum, changement de la sémantique de for.
Voyons ces mots-clés.

Tableau 4. Mots réservés du langage

abstract class extends implements null strictfp true
assert const false import package super try
boolean continue final instanceof private switch void
break default finally int protected synchronized volatile
byte do float interface public this while
case double for long return throw
catch else goto native short throws
char enum if new static transient

Comme de nombreux mots réservés, il n'est pas possible d'utiliser ceux-ci pour autre chose que ce pourquoi ils ont été créés.

5.3. Tests : if et switch

Ces deux instructions sont quasiment identiques à celles du C ou du C++. Le if permet de tester si une valeur est vraie ( true) ou fausse ( false). Si elle est vraie, alors certaines instructions, regroupées dans un bloc, sont exécutées, sinon ce sont d'autres instructions, optionnelles, qui le sont. Le switch fonctionne un peu différemment. Il s'agit d'une instruction de branchement, qui "branche" le code sur une instruction en fonction d'une valeur, constante, que prend le paramètre du switch. La syntaxe du if est la suivante.
 if (expression booléenne)
    commandes on bloc de commandes
 [ else 
    commandes on bloc de commandes ]
Un bloc de commande est une suite de commandes encadrée par des accolades, comme nous l'avons déjà vu. Il est une bonne habitude de programmation de systématiquement utiliser des blocs dans un if, même pour n'enserrer qu'une unique commande. Un else optionnel peut être associé à un if. La syntaxe du switch est la suivante.
 switch (expression)
    case constante1 : commande1 ;
    [ break ; ] 
    [ case constante2 : commande2 ;
    [ break ; ]] 
    [ default : commande ; ]
Dans ce modèle, expression peut être n'importe quelle expression qui retourne un entier de type byte, char, short ou int (le type long n'est pas accepté), ou une valeur énumérée (à partir de Java 5, bien sûr). Les valeurs constante1 , constante2 , etc... sont des valeurs constantes. Ce peut donc être :
  • des valeurs écrites en dur dans le code, ce qui est évidemment à proscrire !
  • des valeurs constantes, donc déclarées comme étant final. Ces valeurs peuvent être importées statiquement.
  • des valeurs énumérées.
Les commandes sont des instructions Java classiques, rangées ou non dans un bloc, cela n'a pas d'importance (en général elles ne le sont pas). La machine Java teste séquentiellement si expression a pour valeur constante1 puis constante2 , etc… Dès qu'elle rencontre une valeur qui correspond, alors elle exécute les commandes qui se trouvent à la suite de cette valeur. Le piège classique du switch, est que la machine Java exécute toutes les commandes du switch sous la constante dans laquelle elle est entrée, indépendamment du fait qu'elles soient rangées sous le bon case ou non. Si l'on ne veut exécuter que les commandes correspondant au bon case (c'est le plus souvent ce que l'on veut faire), alors il faut placer un break à la fin de ces commandes. Enfin, si aucun case ne correspond à la valeur d'expression, alors le branchement s'effectue sur default, s'il existe. En général, default est mis en dernier, après tous les case, mais ce n'est pas une obligation. Voyons un exemple de fonctionnement de switch.

Exemple 54. Utilisation de switch

public  boolean testVoyelle(char c) {  
      
     switch (c) {  
      
         case  'a' :  
         case  'e' :  
         case  'i' :  
         case  'o' :  
         case  'u' :  
         case  'y' :  
          
             return true ;  
  
         default :  
  
             return false ;  
    }  
}

Si c est une voyelle minuscule, le branchement va s'effectuer sur l'un des case, et la méthode retournera true. Dans le cas contraire, le branchement s'effectuera sur le default, et la méthode retournera false. Remarquons que la machine Java accepte de compiler cette méthode, car elle sait détecter, grâce à la présence du default, que la méthode possède une instruction de retour systématiquement utilisée dans le switch. Examinons un autre exemple, en utilisant la classe énumération Civilite, utilisée dans le paragraphe sur les énumérations.

Exemple 55. Utilisation de switch avec une énumération

public  boolean isFemale(Civility civility) {  
      
     switch (civility) {  
      
         case MADAME :  
         case MADEMOISELLE :  
  
             return true ;  
  
         case MONSIEUR :  
              
             return false ;  
    }  
  
     return false ;  
}

Notons tout d'abord que dans ce cas, bien que toutes les valeurs énumérées de notre classe énumération Civility aient bien été utilisées dans le switch, la machine Java n'est pas capable de détecter que le switch retournera systématiquement une valeur. Il nous faut donc ajouter un return à l'extérieur, même s'il n'est jamais atteint. Enfin, voyons un exemple d'utilisation du break utilisé dans un switch.

Exemple 56. Utilisation de switch avec break

public  void testBreak(Civility civility) {  
      
     switch (civility) {  
      
         case MADAME :  
            System.out.println("Madame") ;  
         case MADEMOISELLE :  
            System.out.println("Mademoiselle") ;  
  
         break ;  
  
         case MONSIEUR :  
            System.out.println("Monsieur") ;  
  
         default :  
            System.out.println("Default") ;  
    }  
  
    System.out.println("Sortie de la méthode") ;  
}

Voyons la façon dont ce code s'exécute.
 testBreak(Civility.MADAME) ;
 > Madame
 > Mademoiselle
 > Sortie de la méthode

 testBreak(Civility.MADEMOISELLE) ;
 > Mademoiselle
 > Sortie de la méthode

 testBreak(Civility.MONSIEUR) ;
 > Monsieur
 > Default
 > Sortie de la méthode
Probablement du fait de sa capacité à générer des bugs, l'instruction switch n'est que très peu utilisée dans les programmes.

5.4. Boucles : for, while, do ... while

Il existe trois contrôles de boucles en Java : le for pour les boucles indexées, le while pour les boucles avec test avant l'itération, et le do ... while pour les boucles avec test en fin d'itération. La syntaxe du for est la suivante.
 for (initialisation ; test ; incrémentation)
 	commandes on bloc de commandes
La partie initialisation est une unique instruction Java. Le plus souvent, elle consiste en la déclaration et l'initialisation d'un entier qui servira d'index dans la boucle. On peut initialiser plusieurs variables dans cette instruction, séparées par une virgule. Le bloc test est une expression booléenne, évaluée à chaque itération. Si le résultat de cette évaluation est vrai ( true), alors la commande, ou le bloc de commandes du for sont exécutés. Enfin le bloc incrémentation est exécuté à chaque itération, la plupart du temps il met à jour l'index de la boucle, pour l'incrémenter. Le for fonctionne précisément de la façon suivante :
  1. initialisation est exécuté ;
  2. test est évalué, s'il est false, le programme sort de la boucle ;
  3. la commande, ou le bloc de commandes du for est exécuté ;
  4. incrémentation est exécuté ;
  5. test est évalué, s'il est vrai alors on reprend le processus en 3, sinon on sort de la boucle.
Tout comme pour le if, une bonne habitude de programmation est de systématiquement placer les instructions de la boucle dans un bloc enserré par des accolades. Voyons quelques exemples de boucles for.

Exemple 57. Boucles for

// boucle infinie  
 for ( ; ; ) {   
    ...  
}  
  
 // boucle classique  
 for (int i =  0 ; i <  100 ; i++) {  
    ...  
}  
  
 // itération sur les éléments d'un tableau  
 int [] tableau =  new  int [10] ;  
 for (int i =  0 ; i < tableau.length ; i++) {   
    ...  
}  
  
 // boucle à double index  
 for (int i =  0, j =  1 ; i <  100 ; i +=  2, j +=  2) {  
    ...  
}

Rappelons que l'on a toujours intérêt à limiter le plus possible la portée des variables. Déclarer l'index d'une boucle for à l'intérieur de cette boucle est donc une bonne pratique de programmation. Enfin notons la bonne façon d'itérer sur les valeurs d'un tableau : en utilisant le champ length de ce tableau, même si l'on est sûr d'en connaître la taille. La syntaxe du while est plus simple que celle du for.
 while (expression) commandes
Le fonctionnement de cette commande est le suivant.
  1. expression est évalué, s’il est false alors on sort de la boucle ;
  2. commande est exécuté, et on reprend en 1.
L'expression expression est bien sûr de type booléen. Enfin voici la syntaxe du do ... while.
 do commandes while (expression)
Le fonctionnement de cette commande est le suivant :
  1. commande est exécuté ;
  2. expression est évalué, s’il est false alors on sort de la boucle, sinon on reprend en 1.
La différence fondamentale entre le while et le do ... while, est que le do ... while exécute toujours, au moins une fois, son bloc de commande. Enfin, et on ne le redira jamais assez, mettre un bloc de commande entre accolades, même s'il ne contient qu'une unique commande est une bonne habitude de programmation.

5.5. Commandes continue et break

Ces deux commandes permettent de contrôler l'exécution d'un code à l'intérieur d'un bloc. La commande continue s'utilise dans les boucles for, while et do ... while. Elle ne s'utilise nulle part ailleurs. Lorsque la machine Java rencontre une commande continue, elle interrompt l'exécution de la boucle, et démarre l'itération suivante. S’il s’agit d’une boucle for, elle va donc exécuter la commande incrémentale, s’il s’agit d’un while ou d’un do … while, elle va faire le test pour éventuellement entrer dans l’itération suivante. La fonction de break dans les boucles for, while et do ... while est de stopper l’exécution de la boucle, et d’en sortir immédiatement. Une erreur commune est de penser que break permet de sortir d’un bloc, alors qu’il permet de sortir d’une boucle. Il est possible d'utiliser un label en conjonction avec continue et break, afin de lever une éventuelle ambiguïté dans le cas de boucles imbriquées. On peut poser un label sur une boucle, puis se brancher dessus en le passant à la commande continue ou break. Voyons ceci sur un exemple.

Exemple 58. Utilisation de break sur un label

// cherchons une valeur particulière dans un tableau  
 int valeurRecherchee = ... ;  
 int [][] tableau = ... ;   
  
 // déclaration du label  
recherche :   
 for (int i =  0 ; i < tableau.length ; i++) {  
     for (int j =  0 ; j < tableau[i].length ; j++) {  
         if (tableau[i][j] == valeurRecherchee) {  
             break recherche ;  
        }  
    }  
}

L'utilisation du continue avec un label suit la même syntaxe.

5.6. Commandes return et goto

L’instruction return permet de poursuivre l’exécution du code là où il en était au moment de l’appel à une méthode. Il permet de renvoyer une valeur ou un objet. On peut utiliser un return sans argument dans le cas d'une méthode qui retourne void. Voyons sa syntaxe.
 return [ expression ] ;
Le goto est un mot réservé du langage Java, mais non encore utilisé. L’utilisation de goto est de toute façon à proscrire dans un langage structuré, et encore plus dans un langage objet.
Java langage & API
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
Introduction : un peu d'histoire
1. Java : genèse d'un nouveau langage
Programmer en Java
1. Un premier exemple
1.1. Aperçu général, cycle de vie
1.2. Un premier programme
1.3. Programmes, applets, servlets, etc...
2. Une première classe
2.1. Écriture d'une classe
2.2. Instanciation de cette classe
3. Types de base, classes et objets
3.1. Types de base
3.2. Classes et objets
Classes Object et String
1. Introduction
2. La classe Object
2.1. La méthode toString()
2.2. La méthode clone()
2.3. La méthode equals()
2.4. La méthode hashCode()
2.5. La méthode finalize()
2.6. La méthode getClass()
3. La classe String
3.1. Introduction
3.2. Construction d'un objet de type String
3.3. Concaténation, StringBuffer et StringBuilder
3.4. Concaténations de chaînes de caractères depuis Java 8
3.5. Concaténations de chaînes de caractères depuis Java 11
3.6. Extraction d'une sous-chaîne de caractères
3.7. Comparaison de deux chaînes de caractères
3.8. Méthodes de comparaisons lexicographiques
3.9. Méthode de recherche de caractères
3.10. Méthode de modification de chaîne
3.11. Méthode de duplication
3.12. Support de l'unicode, internationalisation
Structure d'une classe
1. Introduction
2. Classes
2.1. Classes publiques
2.2. Classes internes
2.3. Classe membre
2.4. Classes locales
2.5. Classes anonymes
2.6. Le mot-clé this
3. Éléments statiques
3.1. Champ statique
3.2. Cas des constantes
3.3. Bloc statique
3.4. Classe membre statique
4. Membres d'une classe, visibilité
4.1. Bloc non statique
4.2. Accès à un membre, visibilité
4.3. Les champs
4.4. Signature d'une méthode
4.5. Les méthodes
4.6. Getters et Setters
5. Constructeur, instanciation
5.1. Chargement d'une classe
5.2. Constructeurs d'une classe
5.3. Instanciation d'un objet
5.4. Destruction d'objets
5.5. Le mot-clé final
6. Énumérations
6.1. Déclaration d'une énumération
6.2. Classe énumération
6.3. Méthode toString()
6.4. Méthode valueOf()
6.5. Méthode values()
6.6. Méthode ordinal()
6.7. Méthode compareTo()
6.8. Constructeurs privés
6.9. Classe utilitaire : EnumSet
Noms, opérateurs, tableaux
1. Introduction
2. Identificateurs, noms et expressions
2.1. Identificateurs et noms
2.2. Expressions
3. Opérateurs, ordre d'exécution
3.1. Ordre d'exécution
3.2. Les opérateurs
3.3. Les opérateurs ++ et --
3.4. Les opérateurs % et /
3.5. Les opérateurs <<, >> et >>>
3.6. L'opérateur instanceof
3.7. Les opérateurs &, | et ^
3.8. Les opérateurs && et ||
3.9. L'opérateur ? ... :
3.10. Les opérateurs d'affectation
4. Tableaux
4.1. Création d'un tableau
4.2. Initialisation d'un tableau
4.3. Utilisation d'un tableau comme un Object
4.4. Tableaux de tableaux
4.5. Copie de tableaux
5. Blocs, boucles et contrôles
5.1. Blocs
5.2. Mots-clés réservés
5.3. Tests : if et switch
5.4. Boucles : for, while, do ... while
5.5. Commandes continue et break
5.6. Commandes return et goto
Nombres, précision, calculs
1. Introduction
2. Calculs
2.1. Précision
2.2. Codage des nombres flottants
2.3. Le mot-clé strictfp
2.4. Conversion de types
3. Dépassements de capacité
3.1. Cas des entiers
3.2. Cas des flottants
3.3. Bibliothèques BigInteger et BigDecimal
4. Fonctions mathématiques
4.1. Fonctions usuelles
4.2. Générateurs aléatoires
5. Classes enveloppe
5.1. Associer les types de base à des objets
5.2. Auto-boxing
Héritage, abstraction, interfaces
1. Introduction
2. Abstraction et encapsulation
2.1. Abstraction
2.2. Encapsulation
3. Héritage
3.1. Définition de l'héritage
3.2. Conséquences pour les membres
3.3. Polymorphisme
3.4. Empêcher l'héritage
4. Classes abstraites
5. Interfaces
5.1. Introduction
5.2. Définition
5.3. Java 8 et les interfaces
5.4. Utilisation des interfaces
5.5. Définition de constantes dans les interfaces
5.6. Utilité des interfaces
Packages
1. Introduction
2. Notion de paquet
2.1. Déclaration d’appartenance à un paquet
2.2. Chargement d’une classe
2.3. Choix de nom
3. Archives, chemin de recherche, classpath
3.1. Archives
3.2. Variable CLASSPATH
3.3. Notion de classloader
3.4. Bilan sur les classes chargées
3.5. Visibilité
3.6. Conseils d'écriture, bibliothèque standard
Exceptions
1. Introduction
2. Erreurs et Exceptions
2.1. Classe Throwable, notion de stack trace
2.2. Classe Error
2.3. Classe RuntimException
2.4. Classe Exception
3. Déclenchement d'une exception
3.1. Exceptions déclenchées par la JVM
3.2. Exceptions déclenchées par l'application
4. Capter une exception
4.1. Traiter une exception localement
4.2. Code de captage
5. Créer ses propres exceptions
Entrées / sorties
1. Introduction
2. Notion de fichier
2.1. Introduction
2.2. La classe File
2.3. Construction d'une instance de File
2.4. Méthodes exposées
3. Flux de sortie
3.1. Introduction, notion de flux
3.2. Écriture de caractères, classe Writer
3.3. Bufferisation, construction d'un flux sur un autre
3.4. Utilisation de PrintWriter
3.5. Écriture d'octets, OuputStream
3.6. Écriture de types primitifs : DataOutputStream
3.7. Écriture d'objets : ObjectOutputStream
4. Flux d'entrée
4.1. Introduction
4.2. Lecture de caractères, classe Reader
4.3. Bufferisation, lecture ligne par ligne
4.4. Lecture d'octets : InputStream
4.5. Lecture de types primitifs : DataInputStream
4.6. Lecture d'objets : ObjectInputStream
5. Lecture et écriture de flux croisés
5.1. Introduction
5.2. Lire des caractères sur un flux binaire : classe InputStreamReader
5.3. Écrire des caractères sur un flux binaire : classe OutputStreamWriter
5.4. Remarque sur les jeux de caractères
6. Serialization d'objets
6.1. Enjeu de la sérialization d'objets
6.2. Serialization d'un objet
6.3. Sérialization d'une grappe d'objets
6.4. Première surcharge : méthode writeObject() readObject()
6.5. Deuxième surcharge : utilisation d'un externalizer
6.6. Troisième surcharge : utilisation d'un objet proxy
7. Flux compressés
7.1. Introduction
7.2. Flux de type gzip
7.3. Flux de type zip