1. Interface Iterable
En Java, presque toutes les collections (sauf Map) implémentent l’interface Iterable. Cela signifie qu’on peut les parcourir séquentiellement — élément par élément — sans se soucier des détails de la structure interne. Pour un développeur, cela revient à dire que « la collection dispose d’un moyen intégré de parcourir tous ses éléments ».
L’interface Iterable définit exactement une méthode :
Iterator<E> iterator();
La méthode iterator() renvoie un objet de type Iterator — « assistant » qui sait parcourir la collection pas à pas. C’est grâce à lui que fonctionne la boucle for-each habituelle :
for (ElementType e : collection) {
// ...
}
En coulisses, c’est justement cet Iterator qui fait le travail. Autre avantage : grâce à lui, vous pouvez supprimer des éléments en toute sécurité pendant le parcours via remove(). En essayant de le faire avec un parcours classique, on provoque facilement une ConcurrentModificationException.
2. Interface Iterator
Iterator, c’est un « coursier » qui sait avancer dans votre collection sans sauter d’éléments et en respectant son ordre de parcours.
| Méthode | Description |
|---|---|
|
Y a-t-il encore des éléments à parcourir ? |
|
Retourne l’élément suivant et avance dessus |
|
Supprime l’élément courant en toute sécurité, sans erreurs |
Exemple de parcours d’une collection avec Iterator
import java.util.*;
public class IteratorDemo {
public static void main(String[] args) {
List<String> tasks = new ArrayList<>();
tasks.add("Caresser le chat");
tasks.add("Faire les devoirs");
tasks.add("Regarder une série");
Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
String task = it.next();
System.out.println("Tâche: " + task);
}
}
}
Que se passe-t-il ici ?
- On obtient un itérateur via tasks.iterator().
- Tant qu’il y a un élément suivant (hasNext() renvoie true), on le prend via next() et on l’affiche.
- L’itérateur gère lui‑même l’ordre de parcours — vous n’avez pas besoin de savoir comment la collection stocke ses éléments en interne.
3. Pourquoi utiliser Iterator s’il existe des boucles ?
Avec Iterator, vous pouvez parcourir n’importe quelle collection, même sans index (par exemple un Set). C’est une approche universelle, indépendante du type concret de collection.
Suppression sûre des éléments
Tâche fréquente : parcourir une collection et supprimer certains éléments. Si vous le faites avec for-each, vous pouvez obtenir une erreur :
for (String task : tasks) {
if (task.contains("chat")) {
tasks.remove(task); // BOUM! ConcurrentModificationException
}
}
Pourquoi cela se produit-il ? La collection ne s’attend pas à ce que sa structure soit modifiée directement pendant un parcours initié par un itérateur.
La bonne façon :
Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
String task = it.next();
if (task.contains("chat")) {
it.remove(); // Tout se passera bien !
}
}
Pourquoi ne pas simplement utiliser des index ?
Parce que toutes les collections n’ont pas d’index. Par exemple, HashSet ou TreeSet n’ont pas la notion de « cinquième élément ». Iterator fonctionne toujours — c’est là sa force.
4. Détails sur for-each
La boucle for améliorée (for-each) est apparue dès Java 5. En substance, c’est du sucre syntaxique qui permet de parcourir les éléments de la manière la plus simple :
for (String task : tasks) {
System.out.println("Tâche: " + task);
}
Sous le capot, le compilateur appelle iterator(), vérifie les éléments via hasNext() et les récupère avec next(). Cela se lit littéralement comme une phrase en langage naturel : « pour chaque tâche de la liste ».
Quand for-each ne convient-il pas ?
- Vous devez supprimer des éléments pendant le parcours (for-each ne permet pas d’appeler remove() directement).
- Vous avez besoin de l’index pour, par exemple, remplacer un élément à une position donnée.
- Vous travaillez avec Map — elle manipule des paires « clé–valeur » et nécessite une logique de parcours dédiée.
5. Parcourir une Map : astuces et nuances
L’interface Map n’implémente pas Iterable directement, car il s’agit d’un ensemble de paires « clé–valeur ». Néanmoins, Map fournit des vues pratiques pour le parcours.
Parcours par clés
Map<String, String> users = new HashMap<>();
users.put("alice", "alice@example.com");
users.put("bob", "bob@gmail.com");
for (String login : users.keySet()) {
System.out.println("Identifiant: " + login);
}
Parcours par valeurs
for (String email : users.values()) {
System.out.println("Email: " + email);
}
Parcours par paires (clé–valeur)
La méthode la plus universelle est le parcours via entrySet() :
for (Map.Entry<String, String> entry : users.entrySet()) {
System.out.println("Identifiant: " + entry.getKey() + ", Email: " + entry.getValue());
}
Fait intéressant : Entry est une interface interne de Map avec les méthodes getKey() et getValue(). Ainsi, vous obtenez directement les deux parties de la paire.
Parcours via Iterator
Iterator<Map.Entry<String, String>> it = users.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
// On peut même supprimer l’élément en toute sécurité :
if (entry.getKey().startsWith("v")) {
it.remove();
}
}
6. Exemples concrets : comment le parcours des collections aide dans une application
Exemple : afficher toutes les tâches de l’utilisateur
List<String> tasks = new ArrayList<>();
tasks.add("Faire les devoirs");
tasks.add("Caresser le chat");
tasks.add("Regarder une série");
System.out.println("Vos tâches pour aujourd'hui:");
for (String task : tasks) {
System.out.println("- " + task);
}
Supprimons maintenant toutes les tâches contenant le mot "chat" :
Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
String task = it.next();
if (task.contains("chat")) {
it.remove();
}
}
System.out.println("Tâches restantes:");
for (String task : tasks) {
System.out.println("- " + task);
}
Exemple : parcourir des identifiants uniques via un Set
Set<String> logins = new HashSet<>();
logins.add("alice");
logins.add("bob");
logins.add("claire");
for (String login : logins) {
System.out.println("Utilisateur: " + login);
}
Remarque : l’ordre d’affichage d’un Set peut être quelconque !
Exemple : parcourir une Map pour afficher des utilisateurs
Map<String, String> users = new HashMap<>();
users.put("alice", "alice@example.com");
users.put("bob", "bob@gmail.com");
for (Map.Entry<String, String> entry : users.entrySet()) {
System.out.println("Identifiant: " + entry.getKey() + ", Email: " + entry.getValue());
}
7. Iterator.remove() : suppression sûre des éléments
L’une des erreurs les plus fréquentes des débutants consiste à essayer de supprimer des éléments d’une collection pendant un parcours for-each. L’itérateur résout ce problème grâce à remove().
Comment ça marche ?
- Lors de l’appel de it.remove(), l’élément courant est supprimé — celui renvoyé par le dernier next().
- C’est sûr : la collection ne lève pas de ConcurrentModificationException.
Exemple :
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
int n = it.next();
if (n % 2 == 0) {
it.remove(); // On supprime tous les nombres pairs
}
}
System.out.println(numbers); // [1, 3, 5]
Schéma de parcours d’une collection
+---------+ +---------+ +---------+
| Element | --> | Element | --> | Element | ...
+---------+ +---------+ +---------+
^ ^
| |
next() next()
L’itérateur « avance » de manière séquentielle tant que hasNext() ne renvoie pas false.
9. Erreurs typiques lors de l’utilisation d’Iterator et du parcours des collections
Erreur n° 1 : modification de la collection pendant un for-each.
La tentative de supprimer un élément directement à l’intérieur d’un for-each entraîne une ConcurrentModificationException :
for (String task : tasks) {
if (task.contains("chat")) {
tasks.remove(task); // BOUM! ConcurrentModificationException
}
}
Utilisez Iterator et sa méthode remove().
Erreur n° 2 : appel de remove() avant next().
Il faut d’abord obtenir l’élément courant via next(), sinon l’itérateur ne sait pas quoi supprimer.
Iterator<String> it = tasks.iterator();
it.remove(); // Erreur ! Il faut d'abord un next()
Erreur n° 3 : tentative de parcourir une Map directement dans un for-each.
Map n’implémente pas Iterable directement — utilisez keySet(), values() ou entrySet().
Map<String, String> users = new HashMap<>();
// for (String entry : users) { ... } // Erreur : ce n'est pas possible
for (Map.Entry<String, String> e : users.entrySet()) {
// correct
}
Erreur n° 4 : modifier la collection en dehors de l’itérateur pendant un parcours avec un itérateur.
Pendant le parcours, supprimez les éléments uniquement via it.remove(), et non via les méthodes de la collection.
Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
String task = it.next();
if (task.contains("chat")) {
tasks.remove(task); // Erreur ! Il faut it.remove()
}
}
GO TO FULL VERSION