¿Por qué no obtengo una java.util.ConcurrentModificationException en este ejemplo?

176

Nota: conozco el Iterator#remove()método.

En el siguiente ejemplo de código, no entiendo por qué el método List.removein mainarroja ConcurrentModificationException, pero no en el removemétodo.

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer toRemove) {
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer toRemove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }
}
Bhesh Gurung
fuente
3
La única forma segura de eliminar un elemento de una lista mientras se itera sobre esa lista es mediante el uso Iterator#remove(). ¿Por qué lo haces de esta manera?
Matt Ball el
@MattBall: Solo estaba tratando de ver cuál podría ser la razón aquí. Porque es el mismo "bucle mejorado para" en ambos métodos, pero uno arroja el otro ConcurrentModificationExceptiony el otro no.
Bhesh Gurung
Hay una diferencia en el elemento que eliminas. En el método, eliminas el 'elemento medio'. En general, eliminas el último. Si intercambias los números, obtienes la excepción en tu método. Sin embargo, todavía no estoy seguro de por qué.
Ben van Gompel
Tuve un problema similar, cuando mi ciclo repitió también una posición que no existía después de eliminar un elemento en el ciclo. Simplemente arreglé esto agregando un return;en el bucle.
frank17
en java8 Android, eliminar un elemento que no sea el último invocaría la ConcurrentModificationException. entonces, para su caso, la función remove obtendría una excepción que es opuesta a la que observó anteriormente.
gonglong

Respuestas:

262

He aquí por qué: como se dice en el Javadoc:

Los iteradores devueltos por el iterador de esta clase y los métodos listIterator son rápidos: si la lista se modifica estructuralmente en cualquier momento después de que se crea el iterador, de cualquier manera, excepto a través de los propios métodos remove o add del iterador, el iterador arrojará una ConcurrentModificationException.

Esta comprobación se realiza en el next()método del iterador (como puede ver en el stacktrace). Pero alcanzaremos el next()método solo si se hasNext()entrega verdadero, que es lo que llama cada uno para verificar si se cumple el límite. En su método de eliminación, cuando hasNext()verifica si necesita devolver otro elemento, verá que devuelve dos elementos, y ahora, después de eliminar un elemento, la lista solo contiene dos elementos. Entonces, todo es color de rosa y hemos terminado con la iteración. La verificación de modificaciones concurrentes no ocurre, ya que esto se hace en el next()método que nunca se llama.

Luego llegamos al segundo bucle. Después de eliminar el segundo número, el método hasNext verificará nuevamente si puede devolver más valores. Ya ha devuelto dos valores, pero la lista ahora solo contiene uno. Pero el código aquí es:

public boolean hasNext() {
        return cursor != size();
}

1! = 2, así que continuamos con el next()método, que ahora se da cuenta de que alguien ha estado jugando con la lista y activa la excepción.

Espero que aclare tu pregunta.

Resumen

List.remove()no se lanzará ConcurrentModificationExceptioncuando elimine el segundo último elemento de la lista.

molesto
fuente
55
@pushy: solo responda lo que parece responder a la pregunta que realmente está haciendo, y la explicación es buena. Estoy aceptando esta respuesta y también +1. Gracias.
Bhesh Gurung
42

Una forma de manejarlo es eliminar algo de una copia de Collection(no la Colección en sí), si corresponde. Clonela colección original para hacer una copia a través de a Constructor.

Esta excepción puede ser lanzada por métodos que han detectado la modificación concurrente de un objeto cuando dicha modificación no está permitida.

Para su caso específico, en primer lugar, no creo que finalsea ​​un camino a seguir considerando que tiene la intención de modificar la declaración pasada de la lista

private static final List<Integer> integerList;

También considere modificar una copia en lugar de la lista original.

List<Integer> copy = new ArrayList<Integer>(integerList);

for(Integer integer : integerList) {
    if(integer.equals(remove)) {                
        copy.remove(integer);
    }
}
James Raitsev
fuente
14

El método de reenvío / iterador no funciona al eliminar elementos. Puede eliminar el elemento sin error, pero recibirá un error de tiempo de ejecución cuando intente acceder a los elementos eliminados. No puede usar el iterador porque, como muestra Pushy, causará una excepción ConcurrentModificationException, por lo tanto, use un bucle for regular, pero retroceda.

List<Integer> integerList;
integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

int size= integerList.size();

//Item to remove
Integer remove = Integer.valueOf(3);

Una solución:

Atraviese la matriz en orden inverso si va a eliminar un elemento de la lista. Simplemente yendo hacia atrás a través de la lista, evita visitar un elemento que se ha eliminado, lo que elimina la excepción.

//To remove items from the list, start from the end and go backwards through the arrayList
//This way if we remove one from the beginning as we go through, then we will avoid getting a runtime error
//for java.lang.IndexOutOfBoundsException or java.util.ConcurrentModificationException as when we used the iterator
for (int i=size-1; i> -1; i--) {
    if (integerList.get(i).equals(remove) ) {
        integerList.remove(i);
    }
}
RightHandedMonkey
fuente
brillante idea !
dobrivoje
7

Este fragmento siempre arrojará una ConcurrentModificationException.

La regla es "No puede modificar (agregar o eliminar elementos de la lista) mientras itera sobre él usando un iterador (que ocurre cuando usa un ciclo for-each)".

JavaDocs:

Los iteradores devueltos por el iterador de esta clase y los métodos listIterator son rápidos: si la lista se modifica estructuralmente en cualquier momento después de que se crea el iterador, de cualquier manera, excepto a través de los propios métodos remove o add del iterador, el iterador arrojará una ConcurrentModificationException.

Por lo tanto, si desea modificar la lista (o cualquier colección en general), use el iterador, porque entonces está al tanto de las modificaciones y, por lo tanto, se manejarán correctamente.

Espero que esto ayude.

Bhushan
fuente
3
El OP establece claramente que uno de los bucles NO arroja una excepción y el interrogante fue por qué sucedió eso.
madth3
¿Qué quieres decir con "interrogante"?
Bhushan
4

Tuve el mismo problema, pero en caso de que estuviera agregando un elemento en la lista iterada. Lo hice de esta manera

public static void remove(Integer remove) {
    for(int i=0; i<integerList.size(); i++) {
        //here is maybe fine to deal with integerList.get(i)==null
        if(integerList.get(i).equals(remove)) {                
            integerList.remove(i);
        }
    }
}

Ahora todo va bien porque no creas ningún iterador sobre tu lista, lo iteras "manualmente". Y condicióni < integerList.size() nunca te engañará porque cuando eliminas / agregas algo al tamaño de la Lista de la disminución / incremento de la Lista ...

Espero que ayude, para mí esa fue la solución.

Gondil
fuente
Esto no es verdad ! Evidencia: ejecute este fragmento para ver el resultado: public static void main (String ... args) {List <String> listOfBooks = new ArrayList <> (); listOfBooks.add ("Código completo"); listOfBooks.add ("Código 22"); listOfBooks.add ("22 Efectivo"); listOfBooks.add ("Netbeans 33"); System.err.println ("Antes de eliminar:" + listOfBooks); for (int index = 0; index <listOfBooks.size (); index ++) {if (listOfBooks.get (index) .contains ("22")) {listOfBooks.remove (index); }} System.err.println ("Después de eliminar:" + listOfBooks); }
dobrivoje
1

Si utiliza colecciones de copia en escritura, funcionará; sin embargo, cuando utiliza list.iterator (), el Iterador devuelto siempre hará referencia a la colección de elementos tal como estaba cuando (como se indica a continuación) se llamó a list.iterator (), incluso si otro subproceso modifica la colección. Cualquier método de mutación invocado en un Iterador basado en copia en escritura o ListIterator (como agregar, establecer o eliminar) arrojará una UnsupportedOperationException.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new CopyOnWriteArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}
JohnnyO
fuente
0

Esto funciona bien en Java 1.6

~% javac RemoveListElementDemo.java
~% java RemoveListElementDemo
~% cat RemoveListElementDemo.java

import java.util.*;
public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

~%

battosai
fuente
Perdón por el error tipográfico Esto 'funciona' bien en Java 1.6
battosai
Hmm ... Puede que tengas una implementación diferente. Pero según la especificación se supone que debe hacer eso, OMI. Mira la respuesta de @ Pushy.
Bhesh Gurung
desafortunadamente, la identificación no está en Java 1.8
dobrivoje
0

En mi caso lo hice así:

int cursor = 0;
do {
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
} while (cursor != integerList.size());
Saif Hamed
fuente
0

Cambia Iterator for eacha for looppara resolver.

Y la razón es:

Los iteradores devueltos por el iterador de esta clase y los métodos listIterator son rápidos: si la lista se modifica estructuralmente en cualquier momento después de que se crea el iterador, de cualquier manera, excepto a través de los propios métodos remove o add del iterador, el iterador arrojará una ConcurrentModificationException.

- Documentos Java referidos.

Stephen
fuente
-1

Comprueba tu código hombre ...

En el método principal, está tratando de eliminar el cuarto elemento que no está allí y, por lo tanto, el error. En el método remove (), está tratando de eliminar el tercer elemento que está allí y, por lo tanto, no hay error.

Abhishek
fuente
Estás equivocado: los números 2y 3no son índices para la lista, sino elementos. Ambas lógicas de eliminación verifican equalslos elementos de la lista, no el índice de los elementos. Además, si estuviera relacionado con el índice, lo estaría IndexOutOfBoundsException, no ConcurrentModificationException.
Malte Hartwig