Une tâche, plusieurs outils
Tu as rassemblé des données - dans un ArrayList, un HashSet, un HashMap - et tu veux maintenant visiter chaque élément. Java t'offre plusieurs façons de le faire, et celle que tu choisis dépend de si tu as besoin de l'indice, de si tu dois supprimer des éléments en pleine boucle et de si tu préfères une syntaxe en méthode ou en boucle.
La bonne nouvelle : chaque Collection (liste, ensemble, file) prend en charge la même boucle for améliorée, donc une fois que tu l'as apprise, tu peux toutes les parcourir de la même manière.
Lis le deux-points comme « dans » : « pour chaque lang dans langs ». Tu ne touches jamais à un indice, il n'y a donc rien à rater.
La boucle for-each
Le for amélioré est le choix par défaut pour le simple « fais quelque chose avec chaque élément ». Il se lit clairement et fonctionne de manière identique sur tous les types de collection - ici un HashSet, qui n'a aucun indice :
Une chose à retenir à propos d'un HashSet : il n'a pas d'ordre défini, donc les éléments peuvent s'afficher dans n'importe quel ordre. Le for-each les visite tous exactement une fois, quoi qu'il arrive.
Quand tu as besoin de l'indice
Le for-each te donne l'élément mais pas sa position. Si tu as vraiment besoin de l'indice - pour numéroter des lignes ou pour regarder les éléments voisins - utilise une boucle comptée avec size() et get(i). Cela fonctionne sur une List, qui est positionnelle ; les ensembles et les maps n'ont pas d'indice, donc ce style ne s'applique pas à eux.
N'y recours pas par simple habitude. Si tu n'utilises pas i pour autre chose que get(i), la version for-each est plus courte et plus difficile à rater.
Parcourir une Map
Une Map n'est pas une Collection, tu ne peux donc pas faire un for-each directement dessus. À la place, tu parcours l'une de ses trois vues. La plus courante est entrySet(), qui te livre chaque paire clé-valeur ensemble :
Si tu n'as besoin que des clés, parcours ages.keySet() ; si tu n'as besoin que des valeurs, parcours ages.values(). Préfère entrySet() quand tu as besoin des deux - parcourir les clés puis appeler ages.get(key) à l'intérieur effectue une seconde recherche à chaque itération sans aucune raison.
L'Iterator
Le for-each est en réalité du sucre syntaxique au-dessus d'un Iterator - un objet qui parcourt une collection un élément à la fois via hasNext() et next(). Tu écris rarement cette boucle à la main, avec une exception importante : c'est la façon sûre de supprimer des éléments pendant l'itération.
it.remove() supprime l'élément que next() a renvoyé en dernier, et l'itérateur reste valide. C'est la seule façon autorisée de modifier une collection pendant une boucle manuelle.
Le piège : ConcurrentModificationException
Si tu appelles add ou remove sur la collection elle-même à l'intérieur d'une boucle for-each, tu obtiens une ConcurrentModificationException - l'itérateur remarque que la collection a changé sous lui et refuse de continuer. C'est l'une des erreurs d'exécution de débutant les plus courantes.
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4));
for (int n : nums) {
if (n % 2 == 0) {
nums.remove(Integer.valueOf(n)); // throws ConcurrentModificationException
}
}
La solution est presque toujours removeIf, qui exprime l'intention en une seule ligne et gère l'itération à ta place :
removeIf fonctionne sur n'importe quelle Collection, donc le même appel nettoie aussi un HashSet.
La méthode forEach
Chaque collection possède également une méthode forEach qui prend une lambda et l'exécute sur chaque élément. C'est une alternative plus fonctionnelle, en style expression, à la boucle - pratique pour de courtes lignes uniques :
Remarque que Map.forEach prend directement une lambda à deux arguments (key, value) - pas besoin d'entrySet(). Utilise forEach pour des effets de bord rapides ; reviens à la boucle for classique quand le corps grossit ou que tu as besoin de break pour sortir tôt, ce qu'une lambda ne peut pas faire.
Suite : les méthodes
Tu as maintenant rangé des données dans des collections et tu les as parcourues de toutes les manières que Java propose. L'étape suivante consiste à ranger du comportement : écrire tes propres méthodes pour pouvoir nommer un bloc de logique, lui passer des entrées et le réutiliser - c'est la page suivante.
Questions fréquentes
Comment parcourir une liste en Java ?
La façon la plus propre est la boucle for améliorée (for-each) : for (String s : list) { ... }. Elle fonctionne sur n'importe quelle Collection - ArrayList, HashSet, etc. N'utilise une boucle indexée avec get(i) que lorsque tu as réellement besoin de la position, et un Iterator quand tu dois supprimer des éléments pendant la boucle.
Comment itérer sur une HashMap en Java ?
Une Map n'est pas une Collection, il faut donc parcourir l'une de ses vues. Le choix habituel est for (Map.Entry<K, V> e : map.entrySet()), qui te donne à la fois la clé (e.getKey()) et la valeur (e.getValue()) en une seule passe. Tu peux aussi parcourir map.keySet() pour les clés ou map.values() pour les valeurs.
Pourquoi obtient-on une ConcurrentModificationException pendant une boucle ?
Tu as appelé add ou remove sur la collection alors qu'une boucle for-each l'itérait. La boucle for-each utilise un Iterator en coulisses et celui-ci détecte que la collection a changé structurellement. Corrige cela en supprimant via la méthode remove() de l'Iterator lui-même, ou en appelant removeIf(...) au lieu de boucler.