descargar - java offline
¿Por qué este código no arroja una ConcurrentModificationException? (3)
Esta pregunta ya tiene una respuesta aquí:
¿Por qué este código no arroja una
ConcurrentModificationException
?
Modifica una
Collection
mientras itera a través de ella, sin usar el método
Iterator.remove()
, que es
la única forma segura de eliminar
.
List<String> strings = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String string : strings)
if ("B".equals(string))
strings.remove("B");
System.out.println(strings);
Obtengo el mismo resultado si reemplazo el
ArrayList
con un
LinkedList
.
Sin embargo, si cambio la lista a
("A", "B", "C", "D)
o simplemente
("A", "B")
obtengo la excepción como se esperaba. ¿Qué está pasando? Estoy usando
jdk1.8.0_25
si eso es relevante.
EDITAR
He encontrado el siguiente enlace
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4902078
La sección relevante es
La solución ingenua es agregar comprobaciones de comodificación a hasNext en AbstractList, pero esto duplica el costo de la comprobación de comodificación. Resulta que es suficiente hacer la prueba solo en la última iteración, lo que no agrega prácticamente nada al costo. En otras palabras, la implementación actual de hasNext:
public boolean hasNext() { return nextIndex() < size; }
Se reemplaza por esta implementación:
public boolean hasNext() { if (cursor != size()) return true; checkForComodification(); return false; }
Este cambio no se realizará porque un organismo regulador interno de Sun lo rechazó. El fallo formal indicó que el cambio "ha demostrado el potencial de tener un impacto de compatibilidad significativo sobre el código existente". (El "impacto de compatibilidad" es que la solución tiene el potencial de reemplazar el mal comportamiento silencioso con una ConcurrentModificationException).
@Tavian Barnes tiene toda la razón.
No se puede garantizar que se produzca esta excepción si la modificación concurrente en cuestión no está sincronizada.
Citando de la especificación
java.util.ConcurrentModification
:
Tenga en cuenta que el comportamiento a prueba de fallas no puede garantizarse ya que, en términos generales, es imposible hacer garantías duras en presencia de modificaciones concurrentes no sincronizadas. Las operaciones a prueba de fallas lanzan ConcurrentModificationException con el mejor esfuerzo. Por lo tanto, sería un error escribir un programa que dependiera de esta excepción para su corrección: ConcurrentModificationException debería usarse solo para detectar errores.
Como regla general, las
ConcurrentModificationException
se lanzan cuando se
detecta
la modificación, no se
causa
.
Si nunca accede al iterador después de la modificación, no arrojará una excepción.
Este detalle minucioso hace que
ConcurrentModificationException
sea bastante poco confiable para detectar el mal uso de las estructuras de datos, desafortunadamente, ya que solo se lanzan después de que se ha hecho el daño.
Este escenario no arroja una
ConcurrentModificationException
porque
next()
no recibe una llamada en el iterador creado después de la modificación.
Los bucles For-each son realmente iteradores, por lo que su código realmente se ve así:
List<String> strings = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> iter = strings.iterator();
while(iter.hasNext()){
String string = iter.next();
if ("B".equals(string))
strings.remove("B");
}
System.out.println(strings);
Considere que su código se ejecuta en la lista que proporcionó. Las iteraciones se ven así:
-
hasNext()
devuelve true, enter loop, -> iter se mueve al índice 0, string = "A", no eliminado -
hasNext()
devuelve verdadero, continuar bucle -> iter se mueve al índice 1, cadena = "B", eliminado.strings
ahora tiene longitud 2. -
hasNext()
devuelve falso (iter está actualmente en el último índice, no quedan más índices), salir del bucle.
Por lo tanto, a medida que se lanzan
ConcurrentModificationException
s cuando una llamada a
next()
detecta que se ha realizado una modificación, este escenario evita por poco esa excepción.
Para sus otros dos resultados, tenemos excepciones.
Para
"A", "B", "C", "D"
, después de eliminar "B" todavía estamos en el bucle, y
next()
detecta la
ConcurrentModificationException
modificación
ConcurrentModificationException
, mientras que para
"A", "B"
me imagino es una especie de ArrayIndexOutOfBounds que se captura y se vuelve a lanzar como una
ConcurrentModificationException
hasNext
en el iterador de ArrayList es solo
public boolean hasNext() {
return cursor != size;
}
Después de la llamada de
remove
, el iterador está en el índice 2 y el tamaño de la lista es 2, por lo que informa que la iteración se ha completado.
Sin verificación de modificación concurrente.
Con ("A", "B", "C", "D) o (" A "," B "), el iterador no está en el nuevo final de la lista, por lo que se llama al
next
, y eso arroja la excepción .
ConcurrentModificationException
s son solo una ayuda de depuración.
No puedes confiar en ellos.