«Quiero eliminar un elemento de una lista en un bucle y obtengo ConcurrentModificationException». ¿Te suena?
\nSi alguna vez has programado en Java y has intentado eliminar un elemento de un ArrayList o HashMap durante una iteración (por ejemplo, en un bucle for-each), seguramente has visto esa temible excepción roja. Aparece en el momento más inoportuno, como si Java te dijera: «Oye, ¿qué estás haciendo? ¡No te atrevas a tocar la colección mientras la recorro!».
En Stack Overflow, esta pregunta ha acumulado miles de visitas. Y no es casualidad: el problema parece simple, pero bajo el capó se esconde todo un conjunto de optimizaciones y matices de concurrencia. En este artículo analizaremos cómo eliminar correctamente objetos de una colección en un bucle en Java, sin provocar ConcurrentModificationException, y sin perder rendimiento.
¡Allá vamos!
\n\n¿Por qué ocurre ConcurrentModificationException?
\nRespuesta corta: iteradores fail-fast. La mayoría de las colecciones de Java Collections Framework (ArrayList, HashSet, HashMap, etc.) utilizan un contador interno de modificaciones (modCount). Cuando creas un iterador, este guarda el valor actual de modCount. En cada llamada a next(), el iterador verifica si la colección ha sido modificada externamente (a través de los métodos add() o remove() de la propia colección). Si el contador no coincide, salta la excepción.
Ejemplo de código que fallará con toda seguridad:
\nList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));\nfor (String item : list) {\n if ("B".equals(item)) {\n list.remove(item); // ¡ConcurrentModificationException!\n }\n}\n\n¿Por qué? Porque for-each usa implícitamente un iterador, mientras que list.remove() modifica modCount directamente, sin pasar por el iterador. El iterador no lo perdona.
Método n.º 1: Usar Iterator.remove() — el clásico
\nLa forma más simple y correcta es tomar el iterador explícitamente y llamar a su método remove(). Este método sincroniza el contador interno del iterador con la colección.
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));\nIterator<String> iterator = list.iterator();\nwhile (iterator.hasNext()) {\n String item = iterator.next();\n if ("B".equals(item)) {\n iterator.remove(); // ¡Seguro!\n }\n}\nSystem.out.println(list); // [A, C, D]\n\nVentajas: seguro, funciona para todas las colecciones que soportan iteradores. Desventajas: un poco verboso. Para filtrados simples, se pueden usar enfoques más modernos.
\n\nMétodo n.º 2: Collection.removeIf() — moderno y elegante
\nA partir de Java 8, la mayoría de las colecciones tienen el método removeIf(Predicate filter). Es la forma más concisa de eliminar elementos según una condición.
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));\nlist.removeIf(item -> "B".equals(item));\nSystem.out.println(list); // [A, C, D]\n\nInternamente, removeIf usa un iterador (o un recorrido directo con preservación de índices), por lo que no se producirá ningún ConcurrentModificationException. Este es el método recomendado para casos simples.
Rendimiento: Para ArrayList, el método removeIf funciona en O(n), pero lo hace de manera eficiente: desplaza los elementos en una sola pasada, en lugar de eliminar uno por uno (lo que sería O(n²)).
Método n.º 3: Recorrido inverso por índice (para List)
\nSi por alguna razón no puedes usar un iterador (por ejemplo, necesitas modificar varios elementos simultáneamente o trabajas con código antiguo), puedes recorrer la lista en orden inverso usando un for normal con índice.
List<String> list = new A