Cómo evitar java.util.ConcurrentModificationException al iterar y eliminar elementos de una ArrayList

203

Tengo una ArrayList sobre la que quiero iterar. Al iterar sobre él, tengo que eliminar elementos al mismo tiempo. Obviamente esto arroja a java.util.ConcurrentModificationException.

¿Cuál es la mejor práctica para manejar este problema? ¿Debo clonar la lista primero?

Elimino los elementos no en el bucle en sí, sino en otra parte del código.

Mi código se ve así:

public class Test() {
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff() {
        for (A a : abc) 
        a.doSomething();
    }

    public void removeA(A a) {
        abc.remove(a);
    }
}

a.doSomethingpodría llamar Test.removeA();

Belphegor
fuente

Respuestas:

325

Dos opciones:

  • Cree una lista de valores que desea eliminar, agregue a esa lista dentro del ciclo, luego llame originalList.removeAll(valuesToRemove)al final
  • Use el remove()método en el iterador mismo. Tenga en cuenta que esto significa que no puede usar el bucle for mejorado.

Como ejemplo de la segunda opción, eliminar de una lista cualquier cadena con una longitud mayor que 5:

List<String> list = new ArrayList<String>();
...
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
    String value = iterator.next();
    if (value.length() > 5) {
        iterator.remove();
    }
}
Jon Skeet
fuente
2
Debería haber mencionado que elimino los elementos en otra parte del código y no el bucle en sí.
RoflcoptrException
@Roflcoptr: Bueno, es difícil responder sin ver cómo interactúan los dos bits de código. Básicamente, no puedes hacer eso. No es obvio si clonar la lista primero ayudaría, sin ver cómo todo se junta. ¿Puedes dar más detalles en tu pregunta?
Jon Skeet
Sé que clonar la lista ayudaría, pero no sé si es un buen enfoque. Pero agregaré más código.
RoflcoptrException
2
Esta solución también conduce a java.util.ConcurrentModificationException, consulte stackoverflow.com/a/18448699/2914140 .
CoolMind
1
@CoolMind: Sin múltiples hilos, este código debería estar bien.
Jon Skeet
17

Desde los JavaDocs de ArrayList

Los iteradores devueltos por los métodos iterador y listIterator de esta clase 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.

Varun Achar
fuente
66
¿Y dónde está la respuesta a la pregunta?
Adelin
Como dice, excepto a través de los propios métodos de eliminar o agregar del iterador
Varun Achar
14

Está intentando eliminar el valor de la lista en el "bucle for" avanzado, lo cual no es posible, incluso si aplica algún truco (que hizo en su código). La mejor manera es codificar el nivel de iterador como se recomienda aquí.

Me pregunto cómo la gente no ha sugerido el enfoque tradicional de bucle.

for( int i = 0; i < lStringList.size(); i++ )
{
    String lValue = lStringList.get( i );
    if(lValue.equals("_Not_Required"))
    {
         lStringList.remove(lValue);
         i--; 
    }  
}

Esto también funciona.

suhas0sn07
fuente
2
¡¡¡Esto no es correcto!!! cuando elimina un elemento, el siguiente toma su posición y, mientras aumenta, el siguiente elemento no se verifica en la próxima iteración. En este caso, debe elegir (int i = lStringList.size (); i> -1; i--)
Johntor
1
¡De acuerdo! Alternativo es realizar i--; en condición if dentro del ciclo for.
suhas0sn07
Creo que esta respuesta fue editada para abordar los problemas en los comentarios anteriores, por lo que ahora funciona bien, al menos para mí.
Kira Resari
11

Realmente deberías repetir la matriz de la manera tradicional

Cada vez que elimine un elemento de la lista, los elementos posteriores se impulsarán. Mientras no cambie elementos que no sean el iterativo, el siguiente código debería funcionar.

public class Test(){
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff(){
        for(int i = (abc.size() - 1); i >= 0; i--) 
            abc.get(i).doSomething();
    }

    public void removeA(A a){
        abc.remove(a);
    }
}
Marcus
fuente
10

En Java 8 puede usar la interfaz de colección y hacer esto llamando al método removeIf:

yourList.removeIf((A a) -> a.value == 2);

Más información se puede encontrar aquí

ggeo
fuente
6

Haga el bucle de la manera normal, java.util.ConcurrentModificationExceptiones un error relacionado con los elementos a los que se accede.

Entonces intenta:

for(int i = 0; i < list.size(); i++){
    lista.get(i).action();
}
Tacila
fuente
Evitaste java.util.ConcurrentModificationExceptionno quitando nada de la lista. Difícil. :) Realmente no se puede llamar a esto "la forma normal" para iterar una lista.
Zsolt Sky
6

Mientras itera la lista, si desea eliminar el elemento es posible. Veamos a continuación mis ejemplos,

ArrayList<String>  names = new ArrayList<String>();
        names.add("abc");
        names.add("def");
        names.add("ghi");
        names.add("xyz");

Tengo los nombres anteriores de la lista Array. Y quiero eliminar el nombre "def" de la lista anterior,

for(String name : names){
    if(name.equals("def")){
        names.remove("def");
    }
}

El código anterior arroja la excepción ConcurrentModificationException porque está modificando la lista mientras itera.

Entonces, para eliminar el nombre "def" de Arraylist haciendo esto,

Iterator<String> itr = names.iterator();            
while(itr.hasNext()){
    String name = itr.next();
    if(name.equals("def")){
        itr.remove();
    }
}

El código anterior, a través del iterador, podemos eliminar el nombre "def" de la Arraylist e intentar imprimir la matriz, verá el resultado a continuación.

Salida: [abc, ghi, xyz]

Indra K
fuente
De lo contrario, podemos usar la lista concurrente que está disponible en el paquete concurrente, para que pueda realizar operaciones de quitar y agregar mientras itera. Por ejemplo, vea el fragmento de código a continuación. ArrayList <String> nombres = new ArrayList <String> (); CopyOnWriteArrayList <String> copyNames = new CopyOnWriteArrayList <String> (nombres); for (String name: copyNames) {if (name.equals ("def")) {copyNames.remove ("def"); }}
Indra K
CopyOnWriteArrayList serán las operaciones más costosas.
Indra K
5

Una opción es modificar el removeAmétodo para esto:

public void removeA(A a,Iterator<A> iterator) {
     iterator.remove(a);
     }

Pero esto significaría que doSomething()debería poder pasar iteratorel removemétodo. No es una muy buena idea.

¿Puede hacer esto en un enfoque de dos pasos ?: En el primer bucle, cuando itera sobre la lista, en lugar de eliminar los elementos seleccionados, márquelos como eliminados . Para esto, simplemente puede copiar estos elementos (copia superficial) en otro List.

Luego, una vez que haya terminado su iteración, simplemente haga una removeAlldesde la primera lista todos los elementos en la segunda lista.

Bhaskar
fuente
Excelente, utilicé el mismo enfoque, aunque repito dos veces. hace las cosas simples y no hay problemas concurrentes con él :)
Pankaj Nimgade
1
No veo que Iterator tenga un método remove (a). Remove () no toma argumentos docs.oracle.com/javase/8/docs/api/java/util/Iterator.html ¿qué me estoy perdiendo?
c0der
5

Aquí hay un ejemplo en el que uso una lista diferente para agregar los objetos para su eliminación, luego uso stream.foreach para eliminar elementos de la lista original:

private ObservableList<CustomerTableEntry> customersTableViewItems = FXCollections.observableArrayList();
...
private void removeOutdatedRowsElementsFromCustomerView()
{
    ObjectProperty<TimeStamp> currentTimestamp = new SimpleObjectProperty<>(TimeStamp.getCurrentTime());
    long diff;
    long diffSeconds;
    List<Object> objectsToRemove = new ArrayList<>();
    for(CustomerTableEntry item: customersTableViewItems) {
        diff = currentTimestamp.getValue().getTime() - item.timestamp.getValue().getTime();
        diffSeconds = diff / 1000 % 60;
        if(diffSeconds > 10) {
            // Element has been idle for too long, meaning no communication, hence remove it
            System.out.printf("- Idle element [%s] - will be removed\n", item.getUserName());
            objectsToRemove.add(item);
        }
    }
    objectsToRemove.stream().forEach(o -> customersTableViewItems.remove(o));
}
serup
fuente
Creo que está haciendo un trabajo extra ejecutando dos bucles, en el peor de los casos, los bucles serían de toda la lista. Sería más simple y menos costoso hacerlo en un solo ciclo.
Luis Carlos
No creo que pueda eliminar objetos del primer bucle, de ahí la necesidad de un bucle de eliminación adicional, también el bucle de eliminación es solo objetos para eliminar, tal vez podría escribir un ejemplo con solo un bucle, me gustaría verlo, gracias @ LuisCarlos
serup
Como dice con este código, no puede eliminar ningún elemento dentro del ciclo for porque causa la excepción java.util.ConcurrentModificationException. Sin embargo, podría usar un básico para. Aquí escribo un ejemplo usando parte de su código.
Luis Carlos
1
for (int i = 0; i <customersTableViewItems.size (); i ++) {diff = currentTimestamp.getValue (). getTime () - clientsTableViewItems.get (i) .timestamp.getValue (). getTime (); diffSeconds = diff / 1000% 60; if (diffSeconds> 10) {customersTableViewItems.remove (i--); }} Es importante i-- porque no quieres saltarte ningún elemento. También puede usar el método removeIf (filtro Predicate <? Super E>) proporcionado por la clase ArrayList. Espero esta ayuda
Luis Carlos
1
La excepción ocurre porque en for-loop existe como una referencia activa al iterador de la lista. En lo normal, no hay una referencia y tiene más flexibilidad para cambiar los datos. Espero esta ayuda
Luis Carlos
3

En lugar de usar Para cada ciclo, use normal para ciclo. por ejemplo, el siguiente código elimina todos los elementos en la lista de matriz sin dar java.util.ConcurrentModificationException. Puede modificar la condición en el bucle según su caso de uso.

   for(int i=0;i<abc.size();i++)  {

          e.remove(i);
        }
Shubham Chopra
fuente
2

Haga algo simple como esto:

for (Object object: (ArrayList<String>) list.clone()) {
    list.remove(object);
}
Xlsx
fuente
2

Una solución alternativa de Java 8 usando stream:

        theList = theList.stream()
            .filter(element -> !shouldBeRemoved(element))
            .collect(Collectors.toList());

En Java 7 puedes usar Guava en su lugar:

        theList = FluentIterable.from(theList)
            .filter(new Predicate<String>() {
                @Override
                public boolean apply(String element) {
                    return !shouldBeRemoved(element);
                }
            })
            .toImmutableList();

Tenga en cuenta que el ejemplo de Guava da como resultado una lista inmutable que puede o no ser lo que desea.

Zsolt Sky
fuente
1

También puede usar CopyOnWriteArrayList en lugar de una ArrayList. Este es el último enfoque recomendado por JDK 1.5 en adelante.

Pathikreet
fuente
1

En mi caso, la respuesta aceptada no funciona, detiene la excepción, pero causa algunas inconsistencias en mi lista. La siguiente solución me funciona perfectamente.

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

for (String value: list) {
   if (value.length() > 5) { // your condition
       itemsToRemove.add(value);
   }
}
list.removeAll(itemsToRemove);

En este código, agregué los elementos para eliminar, en otra lista y luego usé el list.removeAllmétodo para eliminar todos los elementos necesarios.

Asad Ali Choudhry
fuente
0

"¿Debería clonar la lista primero?"

Esa será la solución más fácil, eliminar del clon y copiar el clon de nuevo después de la eliminación.

Un ejemplo de mi juego rummikub:

SuppressWarnings("unchecked")
public void removeStones() {
  ArrayList<Stone> clone = (ArrayList<Stone>) stones.clone();
  // remove the stones moved to the table
  for (Stone stone : stones) {
      if (stone.isOnTable()) {
         clone.remove(stone);
      }
  }
  stones = (ArrayList<Stone>) clone.clone();
  sortStones();
}
Arjen Rodenhuis
fuente
2
Los votantes que voten al menos deberían dejar un comentario antes de votar.
OneWorld
2
No hay nada intrínsecamente incorrecto con esta respuesta, esperemos que tal vez stones = (...) clone.clone();sea ​​superfluo. ¿No stones = clone;haría lo mismo?
vikingsteve
Estoy de acuerdo, la segunda clonación es innecesaria. Además, puede simplificar esto iterando en el clon y eliminando elementos directamente de stones. De esta manera, ni siquiera necesita la clonevariable: for (Stone stone : (ArrayList<Stone>) stones.clone()) {...
Zsolt Sky
0

Si su objetivo es eliminar todos los elementos de la lista, puede iterar sobre cada elemento y luego llamar:

list.clear()
Gibolt
fuente
0

Llego tarde, lo sé, pero respondo esto porque creo que esta solución es simple y elegante:

List<String> listFixed = new ArrayList<String>();
List<String> dynamicList = new ArrayList<String>();

public void fillingList() {
    listFixed.add("Andrea");
    listFixed.add("Susana");
    listFixed.add("Oscar");
    listFixed.add("Valeria");
    listFixed.add("Kathy");
    listFixed.add("Laura");
    listFixed.add("Ana");
    listFixed.add("Becker");
    listFixed.add("Abraham");
    dynamicList.addAll(listFixed);
}

public void updatingListFixed() {
    for (String newList : dynamicList) {
        if (!listFixed.contains(newList)) {
            listFixed.add(newList);
        }
    }

    //this is for add elements if you want eraser also 

    String removeRegister="";
    for (String fixedList : listFixed) {
        if (!dynamicList.contains(fixedList)) {
            removeResgister = fixedList;
        }
    }
    fixedList.remove(removeRegister);
}

Todo esto es para actualizar de una lista a otra y puede hacer todo desde una sola lista y, en la actualización del método, verifica ambas listas y puede borrar o agregar elementos entre la lista. Esto significa que ambas listas siempre tienen el mismo tamaño

Bécquer Argüello Flores
fuente
0

Utilice el iterador en lugar de la lista de matrices

Hacer que un conjunto se convierta en iterador con coincidencia de tipos

Y pasar al siguiente elemento y eliminar

Iterator<Insured> itr = insuredSet.iterator();
while (itr.hasNext()) { 
    itr.next();
    itr.remove();
}

Pasar al siguiente es importante aquí, ya que debería tomar el índice para eliminar el elemento.

usuario8009263
fuente
0

¿Qué hay de

import java.util.Collections;

List<A> abc = Collections.synchronizedList(new ArrayList<>());
joseluisbz
fuente
-3

Simplemente agregue un descanso después de su declaración ArrayList.remove (A)

Sebastian Altamirano
fuente
¿Podría por favor agregar alguna explicación?
xskxzr