Pregunta Iterando a través de una Colección, evitando ConcurrentModificationException al eliminar en bucle


Todos sabemos que no puedes hacer esto:

for (Object i : l) {
    if (condition(i)) {
        l.remove(i);
    }
}

ConcurrentModificationException etc ... esto aparentemente funciona a veces, pero no siempre. Aquí hay un código específico:

public static void main(String[] args) {
    Collection<Integer> l = new ArrayList<Integer>();

    for (int i=0; i < 10; ++i) {
        l.add(new Integer(4));
        l.add(new Integer(5));
        l.add(new Integer(6));
    }

    for (Integer i : l) {
        if (i.intValue() == 5) {
            l.remove(i);
        }
    }

    System.out.println(l);
}

Esto, por supuesto, da como resultado:

Exception in thread "main" java.util.ConcurrentModificationException

... a pesar de que varios hilos no lo están haciendo ... De todos modos.

¿Cuál es la mejor solución para este problema? ¿Cómo puedo eliminar un elemento de la colección en un bucle sin lanzar esta excepción?

También estoy usando un arbitrario Collection aquí, no necesariamente un ArrayList, entonces no puedes confiar en get.


1028
2017-10-21 23:23


origen


Respuestas:


Iterator.remove() es seguro, puedes usarlo así:

List<String> list = new ArrayList<>();

// This is a clever way to create the iterator and call iterator.hasNext() like
// you would do in a while-loop. It would be the same as doing:
//     Iterator<String> iterator = list.iterator();
//     while (iterator.hasNext()) {
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
    String string = iterator.next();
    if (string.isEmpty()) {
        // Remove the current element from the iterator and the list.
        iterator.remove();
    }
}

Tenga en cuenta que Iterator.remove() es la única forma segura de modificar una colección durante la iteración; el comportamiento no se especifica si la colección subyacente se modifica de cualquier otra forma mientras la iteración está en progreso.

Fuente: docs.oracle> La interfaz de colección


Y de manera similar, si tienes un ListIterator y quieres añadir artículos, puedes usar ListIterator#addpor la misma razón que puedes usar Iterator#remove- está diseñado para permitirlo.


1466
2017-10-21 23:27



Esto funciona:

Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
    if (iter.next().intValue() == 5) {
        iter.remove();
    }
}

Supuse que como un ciclo foreach es azúcar sintáctico para iterar, usar un iterador no ayudaría ... pero te da esto .remove() funcionalidad.


320
2017-10-21 23:26



Con Java 8 puedes usar el nuevo removeIf método. Aplicado a tu ejemplo:

Collection<Integer> coll = new ArrayList<Integer>();
//populate

coll.removeIf(i -> i.intValue() == 5);

156
2018-05-28 10:11



Como la pregunta ya se ha respondido, es decir, la mejor manera es usar el método de eliminación del objeto iterador, entraré en los detalles del lugar donde se produjo el error. "java.util.ConcurrentModificationException" es aventado.

Cada clase de colección tiene una clase privada que implementa la interfaz Iterator y proporciona métodos como next(), remove() y hasNext().

El código para el próximo se ve algo como esto ...

public E next() {
    checkForComodification();
    try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
    } catch(IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
    }
}

Aquí el método checkForComodification se implementa como

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

Entonces, como puede ver, si intenta explícitamente eliminar un elemento de la colección. En resultado de modCount siendo diferente de expectedModCount, lo que resulta en la excepción ConcurrentModificationException.


38
2018-05-15 19:57



Puede usar el iterador directamente como lo mencionó, o bien mantener una segunda colección y agregar cada elemento que desee eliminar a la nueva colección, luego eliminar AllAll al final. Esto le permite seguir usando el tipo de seguridad del bucle for-each a costa de un mayor uso de memoria y tiempo de CPU (no debería ser un gran problema a menos que tenga realmente listas realmente grandes o una computadora realmente vieja)

public static void main(String[] args)
{
    Collection<Integer> l = new ArrayList<Integer>();
    Collection<Integer> itemsToRemove = new ArrayList<Integer>();
    for (int i=0; i < 10; ++i) {
    l.add(new Integer(4));
    l.add(new Integer(5));
    l.add(new Integer(6));
    }
    for (Integer i : l)
    {
        if (i.intValue() == 5)
            itemsToRemove.add(i);
    }

    l.removeAll(itemsToRemove);
    System.out.println(l);
}

22
2017-10-21 23:32



En tales casos, un truco común es (¿era?) Retroceder:

for(int i = l.size() - 1; i >= 0; i --) {
  if (l.get(i) == 5) {
    l.remove(i);
  }
}

Dicho esto, estoy más que feliz de que tengas mejores formas en Java 8, p. removeIf o filter en las corrientes.


17
2017-08-29 09:56



La misma respuesta que Claudio con un bucle for:

for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
    Object object = it.next();
    if (test) {
        it.remove();
    }
}

14
2017-08-21 12:39



Con Colecciones Eclipse (antes Colecciones GS), el método removeIf definido en MutableCollection trabajará:

MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.lessThan(3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);

Con la sintaxis Lambda de Java 8 esto se puede escribir de la siguiente manera:

MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.cast(integer -> integer < 3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);

La llamada a Predicates.cast() es necesario aquí porque un defecto removeIf método fue agregado en el java.util.Collection interfaz en Java 8.

Nota: Soy un committer para Colecciones Eclipse.


11
2017-12-18 23:08



Haga una copia de la lista existente e itere sobre una copia nueva.

for (String str : new ArrayList<String>(listOfStr))     
{
    listOfStr.remove(/* object reference or index */);
}

6
2018-06-26 05:28



Con un bucle for tradicional

ArrayList<String> myArray = new ArrayList<>();

   for (int i = 0; i < myArray.size(); ) {
        String text = myArray.get(i);
        if (someCondition(text))
             myArray.remove(i);
        else 
             i++;
      }

6
2018-04-16 20:29