0Pricing
Java Academy · Leçon

Itérateurs à échec rapide et tolérants aux modifications

Comprenez ConcurrentModificationException et le comportement à échec rapide, puis découvrez quand utiliser des itérateurs par instantané.

Itérateurs à échec rapide et tolérants aux modifications est une leçon Java Academy gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Java Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Java Academy comprend 4 leçons au total.

Itérateurs à échec rapide et à fonctionnement sûr

Les itérateurs Java sont classés selon leur comportement lorsque la collection sous-jacente est modifiée pendant l'itération : les itérateurs à échec rapide lèvent immédiatement une exception ; les itérateurs à fonctionnement sûr continuent sur une copie instantanée.

Itérateurs à échec rapide

La plupart des collections Java standard (ArrayList, HashMap, TreeSet) utilisent des itérateurs à échec rapide. Ils suivent un modCount, c'est-à-dire un compteur de modifications. Toute modification structurelle pendant l'itération déclenche ConcurrentModificationException.

import java.util.*;

List<String> list = new ArrayList<>(List.of("a", "b", "c"));

try {
    for (String s : list) {
        list.add("x"); // structural modification — throws!
    }
} catch (ConcurrentModificationException e) {
    System.out.println("ConcurrentModificationException caught!");
}
// Same for HashMap, TreeMap, HashSet, etc.

Pourquoi l'échec rapide existe

Le comportement à échec rapide facilite le débogage : il révèle immédiatement les erreurs au lieu de laisser l'itération continuer avec un état corrompu. L'alternative, une corruption silencieuse, est beaucoup plus difficile à déboguer.

// modCount is incremented on every structural change
// (add, remove, clear on ArrayList)
// Iterator records modCount on creation
// On each next(), Iterator checks: if current modCount != expected, throw CME

// This detects bugs like:
List<Integer> nums = new ArrayList<>(List.of(1,2,3,4,5));
for (Integer n : nums) {
    if (n == 3) nums.remove(n); // bug caught immediately
}
// ConcurrentModificationException — not silent wrong results

Suppression sûre : Iterator.remove()

La seule modification sûre pendant une itération fondée sur Iterator est Iterator.remove() : elle supprime le dernier élément renvoyé et met à jour modCount.

List<Integer> nums = new ArrayList<>(List.of(1,2,3,4,5,6));
Iterator<Integer> it = nums.iterator();

while (it.hasNext()) {
    int n = it.next();
    if (n % 2 == 0) it.remove(); // safe: updates modCount
}

System.out.println(nums); // [1, 3, 5]

removeIf : une alternative moderne

Java 8 a ajouté removeIf(Predicate) à Collection : une manière plus claire de supprimer les éléments qui correspondent à une condition, sans gérer manuellement l'itérateur.

List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Ann", "Charlie"));

// Modern: removeIf handles iteration internally
names.removeIf(name -> name.startsWith("A"));
System.out.println(names); // [Bob, Charlie]

// Equivalent but verbose iterator approach:
Iterator<String> it = names.iterator();
while (it.hasNext()) {
    if (it.next().startsWith("A")) it.remove();
}

Itérateurs à fonctionnement sûr : CopyOnWriteArrayList

CopyOnWriteArrayList utilise un itérateur à fonctionnement sûr. Il parcourt une copie instantanée créée au moment de la création de l'itérateur ; les modifications n'affectent pas l'itération en cours.

import java.util.concurrent.CopyOnWriteArrayList;

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.addAll(List.of("a", "b", "c"));

for (String s : list) {
    list.add("x"); // no ConcurrentModificationException!
    System.out.print(s + " "); // prints a, b, c (snapshot)
}
System.out.println();
System.out.println(list); // [a, b, c, x, x, x] — modified copy

Itérateur de ConcurrentHashMap

ConcurrentHashMap utilise des itérateurs faiblement cohérents : ils reflètent l'état à un moment donné pendant ou après la création de l'itérateur ; aucune CME n'est levée, et les mises à jour concurrentes peuvent être visibles ou non.

import java.util.concurrent.ConcurrentHashMap;

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1); map.put("b", 2); map.put("c", 3);

for (Map.Entry<String, Integer> e : map.entrySet()) {
    map.put("d", 4); // no CME — weakly consistent
    System.out.println(e.getKey() + "=" + e.getValue());
}
// May or may not print "d=4" — weakly consistent

Collections.synchronizedList

Collections.synchronizedList n'est PAS à fonctionnement sûr : elle utilise toujours des itérateurs à échec rapide, mais bloque les accès concurrents. Vous devez synchroniser manuellement l'itération.

import java.util.*;

List<String> syncList = Collections.synchronizedList(new ArrayList<>());
syncList.addAll(List.of("a", "b", "c"));

// Must synchronize during iteration!
synchronized (syncList) {
    for (String s : syncList) {
        System.out.println(s);
    }
}
// Without the synchronized block, CME is still possible from another thread

Modèle de l'itérateur par copie instantanée

Créez manuellement une copie instantanée pour parcourir en toute sécurité une collection modifiable sans synchronisation.

import java.util.*;

List<String> original = new ArrayList<>(List.of("a", "b", "c"));

// Take a snapshot copy before iterating
List<String> snapshot = List.copyOf(original);

for (String s : snapshot) {
    // Safe to modify original during snapshot iteration
    original.remove(s);
    System.out.println("Removed: " + s);
}
System.out.println(original); // []

Compromis de CopyOnWrite

CopyOnWriteArrayList est à fonctionnement sûr, mais coûteux lors des écritures. Chaque modification crée une copie complète.

  • Adapté aux : nombreuses lectures et écritures rares (listes d'écouteurs d'événements)
  • À éviter pour : modifications fréquentes et grandes collections

Résumé : échec rapide et fonctionnement sûr

Résumé de la comparaison :

  • À échec rapide : ArrayList, HashMap, TreeMap — lèvent une CME en cas de modification et vérifient modCount
  • À fonctionnement sûr/faiblement cohérents : ConcurrentHashMap, CopyOnWriteArrayList — aucune CME, parcours d'une copie instantanée ou de données concurrentes
  • Méthodes de modification sûres : Iterator.remove(), removeIf(), replaceAll()

En pratique : nettoyage des sessions expirées

Supprimer en toute sécurité les sessions expirées d'une liste concurrente en utilisant les modèles appropriés.

import java.util.*;
import java.util.concurrent.*;

class SessionManager {
    private final CopyOnWriteArrayList<Session> sessions = new CopyOnWriteArrayList<>();

    record Session(String id, long expiresAt) {
        boolean isExpired() { return System.currentTimeMillis() > expiresAt; }
    }

    void add(Session s) { sessions.add(s); }

    // Safe to call from multiple threads while iterating
    void purgeExpired() {
        sessions.removeIf(Session::isExpired);
    }

    List<Session> active() {
        return sessions.stream().filter(s -> !s.isExpired()).toList();
    }
}

Vérification rapide

Quelle exception la modification d'une ArrayList pendant son parcours lève-t-elle ?

Récapitulatif : itérateurs à échec rapide et à fonctionnement sûr

Points essentiels :

  • Les itérateurs à échec rapide (ArrayList, HashMap) lèvent ConcurrentModificationException en cas de modification concurrente
  • Les itérateurs à fonctionnement sûr (CopyOnWriteArrayList, ConcurrentHashMap) autorisent les modifications sans lever d'exception
  • Iterator.remove() est la seule manière sûre de supprimer un élément pendant une itération à échec rapide
  • removeIf() est l'alternative moderne et plus claire à la gestion manuelle de Iterator.remove()
  • CopyOnWriteArrayList convient aux scénarios riches en lectures et rares en écritures
  • ConcurrentHashMap est faiblement cohérente : aucune CME, et les modifications concurrentes peuvent être visibles ou non

Questions Fréquemment Posées

La leçon « Itérateurs à échec rapide et tolérants aux modifications » est-elle gratuite ?

Oui — le texte complet de « Itérateurs à échec rapide et tolérants aux modifications » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Java Academy, passe à CoddyKit PRO. Le cours Java Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Itérateurs à échec rapide et tolérants aux modifications » ?

Comprenez ConcurrentModificationException et le comportement à échec rapide, puis découvrez quand utiliser des itérateurs par instantané. Tu pratiques Java Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer Java Academy ?

Aucune expérience préalable n'est requise. Java Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.

Combien de temps prend la leçon « Itérateurs à échec rapide et tolérants aux modifications » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon Java Academy ?

Oui. Chaque leçon Java Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Contrats d’Iterable et d’Iterator
  2. Implémenter un itérateur personnalisé
  3. ListIterator et parcours bidirectionnel
  4. Itérateurs à échec rapide et tolérants aux modifications
← Retour à Java Academy