Sobes.tech
Middle

¿Cómo evitar la excepción ConcurrentModificationException al recorrer una colección?

sobes.tech AI

Respuesta de la IA

Para evitar ConcurrentModificationException al recorrer una colección, se pueden usar los siguientes enfoques:

  1. Usar colecciones seguras para hilos: java.util.concurrent ofrece alternativas seguras para hilos a las colecciones estándar. Por ejemplo:

    • CopyOnWriteArrayList en lugar de ArrayList
    • CopyOnWriteArraySet en lugar de HashSet
    • ConcurrentHashMap en lugar de HashMap
    // Ejemplo con CopyOnWriteArrayList
    import java.util.concurrent.CopyOnWriteArrayList;
    import java.util.Iterator;
    
    public class ConcurrentCollectionExample {
        public static void main(String[] args) {
            CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");
    
            // El iterador no lanza ConcurrentModificationException
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String element = iterator.next();
                System.out.println(element);
                // Cambiar la colección durante la iteración es seguro
                if (element.equals("B")) {
                    list.remove(element);
                }
            }
            System.out.println("Lista final: " + list);
        }
    }
    
  2. Bloquear la colección: Sincronizar el acceso a la colección usando synchronized o bloqueos java.util.concurrent.locks.

    // Ejemplo con bloque sincronizado
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class SynchronizedCollectionExample {
       public static void main(String[] args) {
           List<String> list = new ArrayList<>();
           list.add("A");
           list.add("B");
           list.add("C");
    
           synchronized (list) { // Bloquea la colección para acceso
               Iterator<String> iterator = list.iterator();
               while (iterator.hasNext()) {
                   String element = iterator.next();
                   System.out.println(element);
                   // No se puede modificar la colección aquí si otro hilo puede acceder
                   // a través de este mismo bloque synchronized
               }
           }
           // Modificar la colección fuera del bloque sincronizado o en otro hilo
           // aún puede causar ConcurrentModificationException durante la iteración paralela
       }
    }
    
  3. Usar el iterador para eliminar elementos: Si necesitas eliminar elementos durante la iteración, usa el método remove() del propio iterador (si la colección lo soporta).

    // Ejemplo de eliminación usando Iterator.remove()
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class IteratorRemoveExample {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");
    
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String element = iterator.next();
                System.out.println(element);
                // Eliminar elemento a través del iterador de forma segura
                if (element.equals("B")) {
                    iterator.remove();
                }
            }
            System.out.println("Lista final: " + list);
        }
    }
    
  4. Recorrer una copia de la colección: Crear una copia de la colección y recorrerla. Los cambios en la colección original no afectarán la iteración sobre la copia.

    // Ejemplo de recorrer una copia
    import java.util.ArrayList;
    import java.util.List;
    
    public class CopyIterationExample {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");
    
            // Crear una copia para recorrer
            List<String> copyList = new ArrayList<>(list);
    
            for (String element : copyList) {
                System.out.println(element);
                // Podemos modificar la colección original durante el recorrido de la copia
                if (element.equals("B")) {
                    list.remove(element);
                }
            }
            System.out.println("Lista final: " + list);
        }
    }
    

La elección del enfoque depende de los requisitos específicos: si necesitas modificar la colección durante la iteración, cuán importante es el rendimiento y si el código funciona en un entorno multihilo. CopyOnWriteArrayList/CopyOnWriteArraySet son adecuados cuando hay muchas más lecturas que escrituras. La bloqueo es apropiado cuando los cambios ocurren con menos frecuencia, pero pueden ser más frecuentes que con CopyOnWrite. Usar Iterator.remove() o recorrer una copia es útil cuando los cambios ocurren solo dentro de una misma iteración o hilo.