Introduction : pourquoi for-each est devenu le "roi" des boucles en Java ?
\n\nSi vous avez programmé en Java ne serait-ce qu'une semaine, vous avez forcément rencontré la construction for (Type var : collection). Cette syntaxe concise est appelée boucle for-each (ou enhanced for loop). Elle est apparue avec Java 5 (2004) et est rapidement devenue la préférée des développeurs.
Mais savez-vous ce qui se passe sous le capot lorsque la JVM rencontre cette construction ? Pourquoi for-each peut-il être plus lent qu'un for classique pour les tableaux ? Et pourquoi lève-t-il parfois une ConcurrentModificationException, même si vous ne supprimez rien ?
Dans cet article, nous analyserons comment fonctionne for-each en Java au niveau du bytecode, le comparerons aux boucles classiques et apprendrons à éviter les pièges typiques. C'est parti !
\n\nSyntaxe et première approche
\n\nLa syntaxe de base est la suivante :
\n\nfor (TypeElement variable : collection_ou_tableau) {\n // corps de la boucle\n}\n\nExemple avec ArrayList :
List<String> noms = Arrays.asList("Anna", "Boris", "Victor");\n\nfor (String nom : noms) {\n System.out.println("Bonjour, " + nom + " !");\n}\n\nSortie :
\n\nBonjour, Anna !\nBonjour, Boris !\nBonjour, Victor !\n\nTout simple ? Oui. Mais derrière cette simplicité se cachent deux mécanismes totalement différents — pour les tableaux et pour les collections.
\n\nComment for-each fonctionne-t-il avec les tableaux ?
\n\nLorsque vous écrivez for-each pour un tableau, le compilateur Java le transforme en un simple for avec un index. Pas de magie.
Votre code :
\n\nint[] nombres = {10, 20, 30};\n\nfor (int num : nombres) {\n System.out.println(num);\n}\n\nAprès compilation (décompilez le bytecode — vérifiez) :
\n\nint[] nombres = new int[]{10, 20, 30};\nfor (int i = 0; i < nombres.length; i++) {\n int num = nombres[i];\n System.out.println(num);\n}\n\nConclusion importante : pour les tableaux, for-each et le for classique sont identiques en termes de performances. Aucun surcoût lié aux itérateurs.
\n\nComment for-each fonctionne-t-il avec les collections (List, Set) ?
\n\nC'est là que ça devient intéressant. Pour les collections, for-each utilise un itérateur.
\n\nVotre code :
\n\nList<String> liste = new ArrayList<>();\nliste.add("Java");\nliste.add("Python");\nliste.add("C++");\n\nfor (String lang : liste) {\n System.out.println(lang);\n}\n\nLe compilateur transforme cela en :
\n\nList<String> liste = new ArrayList<>();\nliste.add("Java");\nliste.add("Python");\nliste.add("C++");\n\nfor (Iterator<String> it = liste.iterator(); it.hasNext(); ) {\n String lang = it.next();\n System.out.println(lang);\n}\n\nAutrement dit :
\n- \n
liste.iterator()est appelé — on obtient un itérateur. \n - La condition est
it.hasNext(). \n - Dans le corps, on a
it.next(). \n - La variable
langest initialisée avec le résultat denext(). \n
Qu'est-ce que cela signifie en pratique ?
\n- \n
- On ne peut pas supprimer d'éléments dans un for-each avec
liste.remove()— vous obtiendrez uneConcurrentModificationException. Utilisezit.remove()ou un for classique. \n - On ne peut pas modifier la collection (ajouter/supprimer) pendant l'itération — même erreur. \n
- Performances : pour
ArrayList, la différence avec un for classique est minime (création d'un objet itérateur). PourLinkedList, for-each est beaucoup plus rapide quefor (int i = 0; i < liste.size(); i++), carget(i)sur LinkedList est en O(n) à chaque appel ! \n
Et pour Map ? Itération sur les paires clé-valeur
\n\nMap n'implémente pas directement Iterable, donc for-each