Quand l'index ne fait que gêner
Une boucle for à compteur vous donne un compteur, une condition et une étape de mise à jour. Mais une grande partie du temps, la position d'un élément ne vous intéresse pas vraiment : vous voulez seulement faire quelque chose avec chacun, dans l'ordre, du début à la fin. Gérer un index pour cela, c'est du travail inutile, et c'est exactement là que les erreurs de décalage d'un cran (off-by-one) se glissent.
La boucle for-each (Java l'appelle le for amélioré) supprime complètement le compteur. Vous nommez une variable, vous la pointez vers une collection, et la boucle vous remet chaque élément à tour de rôle.
Syntaxe de base
La forme est for (Type element : collection). Lisez les deux-points comme le mot « dans » :
Il n'y a pas de i, pas de scores.length, pas de scores[i]. À chaque passage, score est l'élément suivant. La boucle s'exécute une fois par élément et s'arrête automatiquement quand il n'y en a plus : vous ne pouvez ni dépasser la fin ni commencer un élément trop tôt.
Parcourir une liste
La même boucle fonctionne sur tout ce qui est itérable, ce qui inclut List, Set et les autres types de collection. Le type de l'élément se place avant le nom de la variable :
Remarquez que vous n'avez pas eu à savoir ni à vous soucier de savoir si langs repose sur un tableau, une liste chaînée ou autre chose : le for-each fonctionne de la même façon sur toutes. C'est là sa vraie force : une syntaxe lisible pour chaque collection.
var vous évite d'écrire le nom du type
Si le type de l'élément est long ou évident, var laisse le compilateur le déduire pour ne pas vous répéter :
Sans var, cette variable de boucle serait l'imprononçable Map.Entry<String, Integer>. var la garde lisible, et le type reste entièrement vérifié à la compilation : ce n'est pas un type souple et dynamique.
Le piège de la modification
Voici la règle qui piège tout le monde : vous ne pouvez pas ajouter ni retirer d'éléments d'une collection pendant qu'une boucle for-each la parcourt. Le faire lève une ConcurrentModificationException :
List<String> items = new ArrayList<>(List.of("a", "b", "c"));
for (String item : items) {
if (item.equals("b")) {
items.remove(item); // lève ConcurrentModificationException
}
}
La boucle remarque que la liste a changé sous elle et s'interrompt plutôt que de sauter ou de répéter des éléments en silence. Pour supprimer en toute sécurité, descendez jusqu'à un Iterator explicite, qui possède un remove() que la boucle connaît :
Un raccourci courant est items.removeIf(item -> item.equals("b")), qui fait la même chose en une seule ligne.
Lecture seule, pas de réaffectation
Autre limite subtile : affecter une valeur à la variable de boucle ne change que la copie locale, pas la collection. Cela surprend ceux qui viennent de langages où la variable de boucle est une référence vivante :
Si vous devez réécrire dans le tableau, il vous faut l'index, et cela signifie la boucle for à compteur classique : for (int i = 0; i < nums.length; i++) nums[i] = nums[i] * 10;. Pour des éléments objets, vous pouvez muter l'objet vers lequel la variable pointe (en appelant un setter, par exemple), mais vous ne pouvez pas le remplacer dans la collection.
break et continue fonctionnent toujours
Un for-each est une vraie boucle, donc break et continue se comportent exactement comme partout ailleurs : break quitte la boucle, continue saute à l'élément suivant :
Cela affiche keep puis keep : ça saute "skip" et s'arrête à "stop" avant d'atteindre "never". Vous n'êtes donc pas obligé de visiter chaque élément ; vous renoncez simplement à l'index en échange d'un code plus propre.
Suivant : les tableaux
Vous avez maintenant parcouru des tableaux plusieurs fois sans vous attarder sur ce qu'ils sont vraiment : des conteneurs indexés de taille fixe avec ce champ .length. La page suivante revient au début et traite les tableaux comme il se doit : comment les déclarer, les valeurs par défaut avec lesquelles ils commencent, et en quoi leur taille fixe diffère d'un ArrayList extensible.
Questions fréquentes
Qu'est-ce qu'une boucle for-each en Java ?
Une boucle for-each (aussi appelée for amélioré) parcourt chaque élément d'un tableau ou d'une collection sans compteur : for (Type element : collection) { ... }. Lisez-la comme « pour chaque élément de la collection ». Elle est plus propre qu'une boucle for à compteur quand vous avez seulement besoin de chaque élément et jamais de l'index.
Quelle est la différence entre une boucle for et une boucle for-each en Java ?
La boucle for classique utilise un compteur explicite (for (int i = 0; i < arr.length; i++)), vous contrôlez donc l'index et le sens. La boucle for-each, for (Type x : arr), n'a pas d'index : elle visite chaque élément dans l'ordre. Utilisez le for-each pour des parcours en lecture seule, du début à la fin ; utilisez la boucle à compteur quand vous avez besoin de l'index, voulez sauter des éléments ou modifier la structure de la collection.
Pourquoi ma boucle for-each lève-t-elle une ConcurrentModificationException ?
Parce que vous avez appelé add() ou remove() sur la collection pendant que vous l'itériez avec un for-each. La boucle détecte le changement structurel et lève l'exception pour vous protéger d'un comportement indéfini. Pour supprimer des éléments en toute sécurité, utilisez un Iterator explicite et sa méthode remove(), ou rassemblez les éléments à supprimer et retirez-les après la boucle.