Java Academy · Lezione

Iterator fail-fast e fail-safe

Comprenda ConcurrentModificationException, il comportamento fail-fast e quando utilizzare iteratori basati su snapshot.

Lezione 4 di 414 passaggi

Iterator fail-fast e fail-safe è una lezione Java Academy gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Java Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Java Academy include 4 lezioni in totale.

Iterator fail-fast e fail-safe

Gli iterator Java vengono classificati in base al loro comportamento quando la collezione sottostante viene modificata durante l'iterazione: gli iterator fail-fast generano immediatamente un'eccezione; quelli fail-safe continuano a operare su uno snapshot.

Iterator fail-fast

La maggior parte delle collezioni Java standard (ArrayList, HashMap, TreeSet) utilizza iterator fail-fast. Questi tengono traccia di un modCount, ovvero un contatore delle modifiche. Qualsiasi modifica strutturale durante l'iterazione genera 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.

Perché esiste il comportamento fail-fast

Il comportamento fail-fast è uno strumento di debug: porta immediatamente alla luce gli errori invece di consentire all'iterazione di continuare con uno stato corrotto. L'alternativa, ovvero una corruzione silenziosa, è molto più difficile da analizzare.

// 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

Rimozione sicura: Iterator.remove()

L'unica modifica sicura durante un'iterazione basata su Iterator è Iterator.remove(): rimuove l'ultimo elemento restituito e aggiorna 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: l'alternativa moderna

Java 8 ha aggiunto removeIf(Predicate) a Collection: un modo più pulito per rimuovere gli elementi che soddisfano una condizione senza gestire manualmente l'iterator.

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();
}

Iterator fail-safe: CopyOnWriteArrayList

CopyOnWriteArrayList utilizza un iterator fail-safe. L'iterazione avviene su uno snapshot creato al momento della creazione dell'iterator: le modifiche non influiscono sull'iterazione in corso.

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

Iterator di ConcurrentHashMap

ConcurrentHashMap utilizza iterator debolmente coerenti: riflettono lo stato in un momento qualsiasi durante o dopo la loro creazione, non generano CME e possono vedere o non vedere gli aggiornamenti concorrenti.

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 NON è fail-safe: utilizza comunque iterator fail-fast, ma blocca l'accesso concorrente. Deve sincronizzare manualmente l'iterazione.

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

Pattern dell'iterator su snapshot

Crei manualmente uno snapshot per iterare in sicurezza su una collezione modificabile senza sincronizzazione.

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); // []

Compromessi di CopyOnWrite

CopyOnWriteArrayList è fail-safe, ma le scritture sono costose. Ogni modifica crea una copia completa.

  • Adatto per: molte letture e scritture rare (liste di listener di eventi)
  • Non adatto per: modifiche frequenti e collezioni di grandi dimensioni

Riepilogo: fail-fast e fail-safe

Riepilogo del confronto:

  • Fail-fast: ArrayList, HashMap, TreeMap; generano CME in caso di modifica e controllano modCount
  • Fail-safe/debolmente coerenti: ConcurrentHashMap, CopyOnWriteArrayList; non generano CME e iterano su uno snapshot o sui dati concorrenti
  • Metodi di modifica sicuri: Iterator.remove(), removeIf(), replaceAll()

Applicazione pratica: pulizia delle sessioni scadute

Rimuovere in sicurezza le sessioni scadute da una lista concorrente utilizzando i pattern appropriati.

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();
    }
}

Verifica rapida

Quale eccezione genera la modifica di un ArrayList durante l'iterazione?

Riepilogo: iterator fail-fast e fail-safe

Punti chiave:

  • Gli iterator fail-fast (ArrayList, HashMap) generano ConcurrentModificationException in caso di modifica concorrente
  • Gli iterator fail-safe (CopyOnWriteArrayList, ConcurrentHashMap) consentono le modifiche senza generare eccezioni
  • Iterator.remove() è l'unico modo sicuro per rimuovere elementi durante un'iterazione fail-fast
  • removeIf() è l'alternativa moderna e più pulita a Iterator.remove() manuale
  • CopyOnWriteArrayList: sicuro negli scenari con molte letture e poche scritture
  • ConcurrentHashMap: debolmente coerente; non genera CME e può vedere o non vedere le modifiche concorrenti
Gratis per iniziare

Impara Java con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
104
Lezioni
374

Domande Frequenti

La lezione «Iterator fail-fast e fail-safe» è gratuita?

Sì — il testo completo di «Iterator fail-fast e fail-safe» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Java Academy, passa a CoddyKit PRO. Il corso Java Academy include 4 lezioni in totale.

Cosa imparerò in «Iterator fail-fast e fail-safe»?

Comprenda ConcurrentModificationException, il comportamento fail-fast e quando utilizzare iteratori basati su snapshot. Eserciti Java Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Java Academy?

Non è richiesta alcuna esperienza precedente. Java Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.

Quanto tempo richiede la lezione «Iterator fail-fast e fail-safe»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Java Academy?

Sì. Ogni lezione Java Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. I contratti di Iterable e Iterator
  2. Implementazione di un Iterator personalizzato
  3. ListIterator e attraversamento bidirezionale
  4. Iterator fail-fast e fail-safe
← Torna a Java Academy