Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 12

Itération sur les collections : boucles for-each et itérateurs

Recommended resources

Bonjour ! Dans nos leçons précédentes, nous avons exploré les structures de données fondamentales de Java : ArrayList pour les listes ordonnées, HashMap pour les paires clé-valeur, et HashSet pour les ensembles uniques. Maintenant que nous savons comment stocker des données, il nous faut une manière standard et efficace de les parcourir.

L'objectif de cette leçon est de maîtriser les techniques d'itération sur les collections. Nous allons consolider votre utilisation de la boucle for-each (ou "boucle améliorée") en comprenant comment elle fonctionne en arrière-plan, et nous introduirons son mécanisme sous-jacent : l'interface Iterator. Comprendre ces deux approches est essentiel pour écrire du code Java robuste, lisible et performant, quelle que soit la collection utilisée.

1. Pourquoi une simple boucle for n'est pas toujours une bonne idée

Avec votre expérience en programmation, votre premier réflexe pour parcourir une liste pourrait être d'utiliser une boucle for avec un index, comme ceci :

List<String> maListe = new ArrayList<>();
//... ajout d'éléments
for (int i = 0; i < maListe.size(); i++) {
    String element = maListe.get(i);
    System.out.println(element);
}

Cette approche fonctionne parfaitement et est efficace pour une ArrayList, car l'accès à un élément par son index (get(i)) est une opération très rapide (complexité en ).

Cependant, que se passe-t-il si maListe est une LinkedList ? Comme nous l'avons vu, une LinkedList ne stocke pas ses éléments dans un tableau contigu. Pour accéder à l'élément à l'index i, elle doit parcourir la chaîne depuis le début. Cette opération a une complexité en . Exécuter cette opération dans une boucle qui tourne n fois mène à une complexité totale de , ce qui est extrêmement inefficace pour une simple tâche de parcours.

De plus, cette méthode est tout simplement inapplicable pour des collections comme HashSet ou les clés d'une HashMap, qui n'ont pas de notion d'index.

Collections : Introduction et listes

Cet extrait d'un cours de l'EPFL explique précisément pourquoi l'itération par index est une mauvaise pratique pour les collections en général et détaille l'alternative efficace.

Lisez la section "8 Parcours des collections" jusqu'à "8.1 Itérateurs". Concentrez-vous sur l'explication de la complexité en O(n^2) pour une LinkedList et comment la boucle for-each résout ce problème.


2. La boucle for-each : la solution simple et efficace

La boucle for-each (ou "boucle améliorée") est la manière privilégiée de parcourir une collection lorsque vous avez seulement besoin de lire ses éléments. Sa syntaxe est concise et prévient les erreurs liées à la gestion d'index.

Cet organigramme illustre le fonctionnement logique d'une boucle `for-each`. Elle vérifie s'il reste des éléments, traite l'élément courant, et continue jusqu'à ce que la collection soit entièrement parcourue.

Vous l'avez déjà utilisée intuitivement. Voyons comment l'appliquer systématiquement à nos différentes collections.

Pour List et Set :
Le parcours est direct.

List<String> fruits = new ArrayList<>(List.of("Pomme", "Banane", "Orange"));
for (String fruit : fruits) {
    System.out.println(fruit);
}

Set<String> langages = new HashSet<>(Set.of("Java", "Python", "JavaScript"));
for (String langage : langages) {
    System.out.println(langage); // L'ordre n'est pas garanti
}

Pour HashMap :
On ne peut pas itérer directement sur une HashMap. Il faut choisir sur quoi itérer :

  • Les clés : map.keySet()
  • Les valeurs : map.values()
  • Les paires clé-valeur : map.entrySet() (le plus courant)

APPRENDRE LE JAVA #12 - LES HASHMAPS

Dans cette vidéo de Graven - Développement, la fin est dédiée à l'itération sur les HashMap.

Regardez la partie sur les boucles, de la boucle 'for'. Elle montre très clairement comment utiliser keySet() et, surtout, entrySet() pour accéder à la fois aux clés et aux valeurs, ce qui est une technique très utile.

Voici un exemple récapitulatif avec entrySet() :

Map<String, Integer> notes = new HashMap<>();
notes.put("Alice", 18);
notes.put("Bob", 15);
notes.put("Charlie", 20);

// Parcours des paires clé-valeur
for (Map.Entry<String, Integer> entree : notes.entrySet()) {
    String nom = entree.getKey();
    Integer note = entree.getValue();
    System.out.println(nom + " a eu la note de : " + note);
}

3. L'interface Iterator : ce qui se passe en coulisses

La boucle for-each est en réalité du "sucre syntaxique". Le compilateur Java la transforme en un code plus verbeux qui utilise un itérateur.

Un itérateur est un objet dont le seul but est de parcourir une collection, élément par élément. Toutes les collections standard en Java fournissent un itérateur en implémentant l'interface Iterable.

Ce diagramme montre l'évolution des "curseurs" en Java. L'`Iterator`, introduit en Java 1.2, est le mécanisme moderne qui unifie le parcours des collections.

L'interface Iterator définit trois méthodes principales :

  • boolean hasNext(): Retourne true s'il y a encore des éléments à parcourir.
  • E next(): Retourne l'élément suivant et avance le "curseur".
  • void remove(): Supprime de la collection le dernier élément retourné par next(). C'est une fonctionnalité clé que la boucle for-each ne possède pas.

La vidéo suivante démontre parfaitement comment utiliser un itérateur manuellement.

JAVA (Intermédiaire) - 52 - L'interface Iterator en Java

La chaîne "LES TEACHERS DU NET" propose une excellente explication de l'interface Iterator.

Commencez par le rappel qui montre les boucles classiques que nous avons déjà vues. Enchaînez avec l'introduction à l'Iterator. Cette partie est cruciale : elle montre comment obtenir un itérateur depuis une ArrayList et comment l'utiliser avec une boucle while, hasNext() et next(). Enfin, regardez comment cela s'applique à une HashMap, en itérant sur les clés (keySet()) ou les valeurs (values()).

Comparaison du code : for-each vs. Iterator

Une boucle for-each :

List<String> maListe = new ArrayList<>(List.of("A", "B", "C"));
for (String element : maListe) {
    System.out.println(element);
}

Est équivalente au code Iterator suivant :

List<String> maListe = new ArrayList<>(List.of("A", "B", "C"));
Iterator<String> it = maListe.iterator(); // 1. Obtenir l'itérateur
while (it.hasNext()) { // 2. Vérifier s'il y a un élément suivant
    String element = it.next(); // 3. Obtenir l'élément et avancer
    System.out.println(element);
}

Alors, quand utiliser un itérateur explicite ? Principalement lorsque vous avez besoin de supprimer des éléments d'une collection pendant que vous la parcourez. Tenter de faire maListe.remove(element) dans une boucle for-each lèvera une ConcurrentModificationException. La seule manière sûre de le faire est d'utiliser it.remove().

List<String> langages = new ArrayList<>(List.of("Java", "PHP", "JavaScript", "Cobol"));

Iterator<String> it = langages.iterator();
while (it.hasNext()) {
    String langage = it.next();
    if (langage.equals("PHP") || langage.equals("Cobol")) {
        // Suppression sûre de l'élément courant
        it.remove();
    }
}
System.out.println(langages); // Affiche [Java, JavaScript]

4. Un mot sur ListIterator

Pour les collections de type List, il existe une version améliorée de l'itérateur appelée ListIterator. Elle hérite de Iterator et ajoute des fonctionnalités spécifiques aux listes :

  • Parcours dans les deux sens (hasPrevious(), previous()).
  • Obtention de l'index (nextIndex(), previousIndex()).
  • Modification de la liste (add(), set()).

Vous ne l'utiliserez pas tous les jours, mais il est bon de savoir qu'il existe pour des cas d'usage plus complexes sur les listes.


Conclusion : Quelle méthode choisir ?

Nous avons vu les différentes façons de parcourir les collections en Java. Voici la règle à suivre, également suggérée dans la ressource de l'EPFL :

  1. Par défaut, utilisez toujours la boucle for-each. Elle est plus lisible, plus concise et moins sujette aux erreurs. Elle est parfaite pour 95% des cas où vous voulez simplement lire les éléments.
  2. Utilisez un Iterator explicite avec une boucle while uniquement lorsque vous avez besoin de supprimer des éléments de la collection pendant le parcours. C'est son principal cas d'usage.
  3. Évitez les boucles avec index (for (int i=...) sur les collections, à moins d'avoir une raison très spécifique et d'être certain de travailler avec une ArrayList.

Points clés à retenir :

  • La boucle for-each est la méthode standard et la plus sûre pour lire les éléments d'une collection.
  • Elle fonctionne sur n'importe quel objet implémentant l'interface Iterable (List, Set, Map via keySet, etc.).
  • Sous le capot, la boucle for-each utilise un Iterator.
  • L'Iterator est un objet qui permet de parcourir une collection et, surtout, de supprimer des éléments en toute sécurité pendant l'itération avec sa méthode remove().

Dans la prochaine leçon, nous conclurons ce module sur les collections. Maintenant que vous savez créer, remplir et parcourir les principales structures de données, nous apprendrons à choisir la structure la plus appropriée (List, Set ou Map) en fonction des besoins spécifiques d'un problème.