0Pricing
Java Academy · Lección

Iteradores fail-fast frente a fail-safe

Comprenda ConcurrentModificationException, el comportamiento fail-fast y cuándo usar iteradores de instantánea.

Iteradores fail-fast frente a fail-safe es una lección gratuita de Java Academy en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Java Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Java Academy incluye 4 lecciones en total.

Iteradores fail-fast y fail-safe

Los iteradores de Java se clasifican según su comportamiento cuando se modifica la colección subyacente durante el recorrido: los iteradores fail-fast lanzan una excepción inmediatamente; los iteradores fail-safe continúan sobre una instantánea.

Iteradores fail-fast

La mayoría de las colecciones estándar de Java (ArrayList, HashMap, TreeSet) usan iteradores fail-fast. Realizan un seguimiento de modCount, un contador de mutaciones. Cualquier modificación estructural durante el recorrido activa 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.

Por qué existe fail-fast

El comportamiento fail-fast ayuda a depurar: revela los errores inmediatamente en lugar de permitir que el recorrido continúe con un estado dañado. La alternativa, una corrupción silenciosa, es mucho más difícil de depurar.

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

Eliminación segura: Iterator.remove()

La única modificación segura durante una iteración basada en Iterator es Iterator.remove(): elimina el último elemento devuelto y actualiza 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: alternativa moderna

Java 8 añadió removeIf(Predicate) a Collection: una forma más clara de eliminar los elementos que cumplen una condición sin gestionar manualmente el iterador.

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

Iteradores fail-safe: CopyOnWriteArrayList

CopyOnWriteArrayList usa un iterador fail-safe. Recorre una instantánea tomada cuando se crea el iterador; las modificaciones no afectan al recorrido en curso.

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

Iterador de ConcurrentHashMap

ConcurrentHashMap usa iteradores débilmente consistentes: reflejan el estado en algún momento durante o después de la creación del iterador, no lanzan CME y pueden reflejar o no las actualizaciones simultáneas.

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 NO es fail-safe: sigue usando iteradores fail-fast, aunque bloquea el acceso simultáneo. Debe sincronizar manualmente durante el recorrido.

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

Patrón de iterador basado en instantánea

Cree manualmente una instantánea para recorrer de forma segura una colección mutable sin sincronización.

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

Compromisos de CopyOnWrite

CopyOnWriteArrayList es fail-safe, pero las escrituras son costosas. Cada mutación crea una copia completa.

  • Adecuado para: muchas lecturas y pocas escrituras, como listas de oyentes de eventos
  • No adecuado para: mutaciones frecuentes o colecciones grandes

Resumen: fail-fast frente a fail-safe

Resumen de la comparación:

  • Fail-fast: ArrayList, HashMap, TreeMap; lanzan CME al modificarse la colección y comprueban modCount
  • Fail-safe o débilmente consistentes: ConcurrentHashMap, CopyOnWriteArrayList; no lanzan CME y recorren una instantánea o datos simultáneos
  • Métodos de modificación segura: Iterator.remove(), removeIf(), replaceAll()

Práctica: limpieza de sesiones caducadas

Eliminar de forma segura las sesiones caducadas de una lista simultánea mediante patrones adecuados.

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

Comprobación rápida

¿Qué excepción lanza ArrayList al modificarla durante un recorrido?

Recapitulación: iteradores fail-fast y fail-safe

Aspectos clave:

  • Los iteradores fail-fast (ArrayList, HashMap) lanzan ConcurrentModificationException cuando se produce una modificación simultánea
  • Los iteradores fail-safe (CopyOnWriteArrayList, ConcurrentHashMap) permiten modificaciones sin lanzar excepciones
  • Iterator.remove() es la única forma segura de eliminar elementos durante una iteración fail-fast
  • removeIf() es la alternativa moderna y más clara a Iterator.remove() manual
  • CopyOnWriteArrayList: adecuado para escenarios con muchas lecturas y pocas escrituras
  • ConcurrentHashMap: débilmente consistente; no lanza CME y puede reflejar o no las modificaciones simultáneas

Preguntas frecuentes

¿La lección «Iteradores fail-fast frente a fail-safe» es gratis?

Sí — el texto completo de «Iteradores fail-fast frente a fail-safe» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Java Academy, actualiza a CoddyKit PRO. El curso de Java Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Iteradores fail-fast frente a fail-safe»?

Comprenda ConcurrentModificationException, el comportamiento fail-fast y cuándo usar iteradores de instantánea. Practicas Java Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Java Academy?

No se requiere experiencia previa. Java Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.

¿Cuánto tiempo toma la lección «Iteradores fail-fast frente a fail-safe»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Java Academy?

Sí. Cada lección de Java Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Contratos de Iterable e Iterator
  2. Implementación de un iterador personalizado
  3. ListIterator y recorrido bidireccional
  4. Iteradores fail-fast frente a fail-safe
← Volver a Java Academy