Penser en Java 2nde édition | - | Sommaire | Préface | Avant-propos | Chapitre : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Annexe : A B C D | Tables des matières | - | Thinking in Java |
Les instructions de contrôle de boucle while, do-while et for sont souvent appelés instructions d'itération. Une instruction est répétée jusqu'à ce que l'expression booléenne de contrôle devienne fausse. Voici la forme d'une boucle while :
expression booléenne est évaluée à l'entrée de la boucle, puis après chaque itération ultérieure d'instruction.
Voici un exemple simple qui génère des nombres aléatoires jusqu'à l'arrivée d'une condition particulière :
Ce test utilise la méthode static random( ) de la bibliothèque Math, qui génère une valeur double comprise entre 0 et 1. (0 inclus, 1 exclu). L'expression conditionnelle de la boucle while signifie « continuer l'exécution de cette boucle jusqu'à ce que le nombre généré soit égal ou supérieur à 0.99 ». Le résultat est une liste de nombre, et la taille de cette liste est différente à chaque exécution du programme.
Voici la forme de la boucle do-while :
La seule différence entre while et do-while est que dans une boucle do-while, instruction est exécutée au moins une fois, même si l'expression est fausse la première fois. Dans une boucle while, si l'expression conditionnelle est fausse la première fois, l'instruction n'est jamais exécutée. En pratique, la boucle do-while est moins utilisée que while.
La boucle for effectue une initialisation avant la première itération. Puis elle effectue un test conditionnel et, à la fin de chaque itération, une « instruction d'itération ». Voici la forme d'une boucle for :
Chacune des expressions instruction d'initialisation, expression booléenne et instruction d'itération peut être vide. expression booléenne est testée avant chaque itération, et dès qu'elle est évaluée à false l'exécution continue à la ligne suivant l'instruction for. À la fin de chaque boucle, instruction d'itération est exécutée.
Les boucles for sont généralement utilisées pour les tâches impliquant un décompte :
Remarquons que la variable c est définie à l'endroit où elle est utilisée, à l'intérieur de l'expression de contrôle de la boucle for, plutôt qu'au début du bloc commençant à l'accolade ouvrante. La portée de c est l'expression contrôlée par la boucle for.
Les langages procéduraux traditionnels tels que C exigent que toutes les variables soient définies au début d'un bloc afin que le compilateur leur alloue de l'espace mémoire lorsqu'il crée un bloc. En Java et C++ les déclarations de variables peuvent apparaître n'importe où dans le bloc, et être ainsi définies au moment où on en a besoin. Ceci permet un style de code plus naturel et rend le code plus facile à comprendre.
Il est possible de définir plusieurs variables dans une instruction for, à condition qu'elles aient le même type :
La définition int de l'instruction for s'applique à la fois à i et à j. La possibilité de définir des variables dans une expression de contrôle est limitée à la boucle for. On ne peut l'utiliser dans aucune autre instruction de sélection ou d'itération.
Au début de ce chapitre j'ai déclaré que l'opérateur virgule (à distinguer du séparateur virgule, utilisé pour séparer les définitions et les arguments de fonctions) avait un seul usage en Java, à savoir dans l'expression de contrôle d'une boucle for. Aussi bien la partie initialisation que la partie itération de l'expression de contrôle peuvent être formées de plusieurs instructions séparées par des virgules, et ces instructions seront évaluées séquentiellement. L'exemple précédent utilisait cette possibilité. Voici un autre exemple :
En voici le résultat :
remarquez que la partie initialisation ainsi que la partie itération sont évaluées en ordre séquentiel. La partie initialisation peut comporter n'importe quel nombre de définitions du même type.
Le déroulement de toutes les instructions d'itération peut être contrôlé de l'intérieur du corps de la boucle au moyen des instructions break et continue. L'instruction break sort de la boucle sans exécuter la suite des instructions. L'instruction continue arrête l'exécution de l'itération courante, et l'exécution reprend en début de boucle avec l'itération suivante.
Ce programme montre des exemples d'utilisation des instructions break et continue dans des boucles for et while :
Dans la boucle for la valeur de i n'atteint jamais 100 car l'instruction break termine la boucle lorsque i prend la valeur 74. En principe, il ne faudrait pas utiliser break de cette manière, à moins que l'on ne connaisse pas le moment où la condition de fin arrivera. L'instruction continue provoque un branchement au début de la boucle d'itération (donc en incrémentant i) chaque fois que i n'est pas divisible par 9. Lorsqu'il l'est, la valeur est imprimée.
La seconde partie montre une « boucle infinie » qui, théoriquement, ne devrait jamais s'arrêter. Toutefois, elle contient une instruction break lui permettant de le faire. De plus, l'instruction continue retourne au début de la boucle au lieu d'exécuter la fin, ainsi la seconde boucle n'imprime que lorsque la valeur de i est divisible par 10. La sortie est :
La valeur 0 est imprimée car 0 % 9 a pour résultat 0.
Il existe une seconde forme de boucle infinie, c'est for(;;). Le compilateur traite while(true) et for(;;) de la même manière, le choix entre l'une et l'autre est donc affaire de goût.
Le mot clef goto est aussi ancien que les langages de programmation. En effet, goto a été le premier moyen de contrôle des programmes dans les langages assembleur : « si la condition A est satisfaite, alors sauter ici, sinon sauter là ». Lorsqu'on lit le code assembleur finalement généré par n'importe quel compilateur, on voit qu'il comporte beaucoup de sauts. Toutefois, un goto au niveau du code source est un saut, et c'est ce qui lui a donné mauvaise réputation. Un programme n'arrêtant pas de sauter d'un point à un autre ne peut-il être réorganisé afin que le flux du programme soit plus séquentiel ? goto tomba en disgrâce après la publication du fameux article « Le goto considéré comme nuisible » écrit par Edsger Dijkstra, et depuis lors les guerres de religion à propos de son utilisation sont devenues monnaie courante, les partisans du mot clef honni recherchant une nouvelle audience.
Comme il est habituel en de semblables situations, la voie du milieu est la plus indiquée. Le problème n'est pas d'utiliser le goto, mais de trop l'utiliser - dans quelques rares situations le goto est réellement la meilleure façon de structurer le flux de programme.
Bien que goto soit un mot réservé de Java, on ne le trouve pas dans le langage ; Java n'a pas de goto. Cependant, il existe quelque chose qui ressemble à un saut, lié aux mots clefs break et continue. Ce n'est pas vraiment un saut, mais plutôt une manière de sortir d'une instruction d'itération. On le présente dans les discussions sur le goto parce qu'il utilise le même mécanisme : une étiquette.
Une étiquette est un identificateur suivi du caractère deux points, comme ceci :
En Java, une étiquette ne peut se trouver qu'en un unique endroit : juste avant une instruction d'itération. Et, j'insiste, juste avant - il n'est pas bon de mettre une autre instruction entre l'étiquette et la boucle d'itération. De plus, il n'y a qu'une seule bonne raison de mettre une étiquette avant une itération, c'est lorsqu'on a l'intention d'y nicher une autre itération ou un switch. Ceci parce que les mots clefs break et continue ont pour fonction naturelle d'interrompre la boucle en cours, alors qu'utilisés avec une étiquette ils interrompent la boucle pour se brancher à l'étiquette :
Cas 1 : break interrompt l'itération intérieure, on se retrouve dans l'itération extérieure. Cas 2 : continue branche à l'itération intérieure. Mais, cas 3 : continue label1 interrompt l'itération intérieure et l'itération extérieure, et branche dans tous les cas à label1. En fait, l'itération continue, mais en redémarrant à partir de l'itération extérieure. Cas 4 : break label1 interrompt lui aussi dans tous les cas et branche à label1, mais ne rentre pas à nouveau dans l'itération. En fait il sort des deux itérations.
Voici un exemple utilisant les boucles for :
Ce programme utilise la méthode prt( ) déjà définie dans d'autres exemples.
Noter que break sort de la boucle for, et l'expression d'incrémentation ne sera jamais exécutée avant le passage en fin de boucle for. Puisque break saute l'instruction d'incrémentation, l'incrémentation est réalisée directement dans le cas où i == 3. Dans le cas i == 7, l'instruction continue outer saute elle aussi en tête de la boucle, donc saute l'incrémentation, qui est, ici aussi, réalisée directement.
Voici le résultat :
Si l'instruction break outer n'était pas là, il n'y aurait aucun moyen de se brancher à l'extérieur de la boucle extérieure depuis la boucle intérieure, puisque break utilisé seul ne permet de sortir que de la boucle la plus interne (il en est de même pour continue).
Évidemment, lorsque break a pour effet de sortir de la boucle et de la méthode, il est plus simple d'utiliser return.
Voici une démonstration des instructions étiquetées break et continue avec des boucles while :
Les mêmes règles s'appliquent au while :
Le résultat de cette méthode éclaircit cela :
Il est important de se souvenir qu'il n'y a qu'une seule raison d'utiliser une étiquette en Java, c'est lorsqu'il existe plusieurs boucles imbriquées et que l'on veut utiliser break ou continue pour traverser plus d'un niveau d'itération.
Dijkstra, dans son article « Le goto considéré comme nuisible », critiquait les étiquettes, mais pas le goto lui-même. Il avait observé que le nombre de bugs semblait augmenter avec le nombre d'étiquettes d'un programme. Les étiquettes et les goto rendent difficile l'analyse statique d'un programme, car ils introduisent des cycles dans le graphe d'exécution de celui-ci. Remarquons que les étiquettes Java ne sont pas concernées par ce problème, dans la mesure où leur emplacement est contraint, et qu'elles ne peuvent pas être utilisées pour réaliser un transfert de contrôle à la demande. Il est intéressant de noter que nous nous trouvons dans un cas où une fonctionnalité du langage est rendue plus utile en en diminuant la puissance.
On parle parfois de switch comme d'une instruction de sélection. L'instruction switch sélectionne un morceau de code parmi d'autres en se basant sur la valeur d'une expression entière. Voici sa forme :
sélecteur-entier est une expression qui produit une valeur entière. switch compare le résultat de sélecteur-entier avec chaque valeur-entière. S'il trouve une égalité, l'instruction correspondante (simple ou composée) est exécutée. Si aucune égalité n'est trouvée, l'instruction qui suit default est exécutée.