Aikido

Por qué deberías usar patrones seguros al eliminar elementos de colecciones

Legibilidad

Regla
Uso métodos métodos cuando eliminar de colecciones.
Modificar una colección mientras iterar sobre ella a menudo provoca errores.

Idiomas compatibles: PY, Java, C/C++, C#, 
Swift/Objective-C, Ruby, PHP, Kotlin, Go,
Scala, Rust, Groovy, Dart, Julia, Elixit, 
Erlang, Clojure, OCaml, Lua

Introducción

Eliminar elementos de una colección durante la iteración causa excepciones de modificación concurrente en Java y un comportamiento impredecible en C#. El iterador mantiene un puntero interno que se invalida cuando la colección subyacente cambia. Esto lleva a elementos omitidos, fallos o bucles infinitos, dependiendo del tipo de colección y el patrón de eliminación utilizado.

Por qué es importante

Estabilidad del sistema: Las excepciones de modificación concurrente bloquean la aplicación inmediatamente. En producción, esto significa solicitudes perdidas e indisponibilidad del servicio. La excepción a menudo ocurre en casos límite con datos específicos, lo que dificulta su detección durante las pruebas.

Integridad de los datos: Cuando la lógica de eliminación falla a mitad de una iteración, la colección queda en un estado parcialmente modificado. Algunos elementos se eliminan, mientras que otros que deberían haberse eliminado permanecen. Esto crea datos inconsistentes que afectan la lógica posterior.

Complejidad de la depuración: Los errores de modificación concurrente dependen del tiempo y solo pueden manifestarse con ciertas combinaciones de datos. Son difíciles de reproducir de forma consistente, lo que los hace difíciles de depurar y corregir de manera fiable.

Ejemplos de código

❌ No conforme:

List<User> users = getUserList();
for (User user : users) {
    if (!user.isActive()) {
        users.remove(user); // ConcurrentModificationException
    }
}

Por qué es incorrecto: Eliminando de usuarios mientras que la iteración con un bucle for mejorado causa ConcurrentModificationException. El iterador detecta que la colección fue modificada fuera del iterador y lanza una excepción inmediatamente. Cualquier usuario activo después del primero inactivo nunca es procesado.

✅ Conforme:

List<User> users = getUserList();
Iterator<User> iterator = users.iterator();
while (iterator.hasNext()) {
    User user = iterator.next();
    if (!user.isActive()) {
        iterator.remove(); // Safe removal through iterator
    }
}

¿Por qué esto importa? Uso iterator.remove() elimina elementos de forma segura durante la iteración. El iterador mantiene un estado consistente y continúa procesando los elementos restantes. Todos los usuarios inactivos se eliminan correctamente sin excepciones.

Conclusión

Utilice iteradores remove() método para una eliminación segura durante la iteración. Alternativamente, utilice streams con filter() para crear nuevas colecciones o removeIf() para eliminación masiva. Nunca llame a la colección de remove() directamente mientras se itera.

Preguntas frecuentes

¿Tiene preguntas?

¿Qué hay de usar bucles for regulares con índice?

Iterar hacia atrás con un índice funciona: for (int i = list.size() - 1; i >= 0; i--). Eliminar elementos desplaza los elementos subsiguientes, pero la iteración hacia atrás evita saltos. Sin embargo, `iterator.remove()` o `removeIf()` son más claros y menos propensos a errores.

¿Puedo usar Java 8+ removeIf en su lugar?

Sí, users.removeIf(user -> !user.isActive()) es el enfoque moderno preferido. Es más conciso y gestiona la iteración de forma segura internamente. Usa removeIf() cuando elimines basándote en un predicado, streams para transformaciones y métodos de iterador cuando la lógica de eliminación sea compleja.

¿Esto se aplica a todos los tipos de colección?

Sí, ArrayList, HashSet, HashMap y la mayoría de las colecciones lanzan ConcurrentModificationException cuando se modifican durante la iteración. Las colecciones thread-safe como ConcurrentHashMap permiten la modificación, pero tienen semánticas diferentes. Consulta siempre la documentación de la colección para conocer las reglas de modificación.

¿Qué pasa con las colecciones de C#?

C# lanza InvalidOperationException cuando las colecciones se modifican durante la iteración. Use ToList() para crear una copia antes de iterar: foreach (var user in users.ToList()) y luego elimine del original. O use LINQ: users = users.Where(u => u.IsActive).ToList().

¿Cómo elimino múltiples elementos de forma eficiente?

Utilice removeIf() para colecciones individuales o streams para filtrado complejo: users = users.stream().filter(User::isActive).collect(Collectors.toList()). Estos enfoques están optimizados para operaciones masivas y son más eficientes que eliminar elementos uno por uno en un bucle.

Asegúrate ahora.

Proteja su código, la nube y el entorno de ejecución en un único sistema central.
Encuentre y corrija vulnerabilidades de forma rápida y automática.

No se requiere tarjeta de crédito | Resultados del escaneo en 32 segundos.