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 l = new ArrayList(); 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 una Collection arbitraria aquí, no necesariamente una ArrayList , por lo que no puede confiar en get .

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

 List 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 iterator = list.iterator(); // while (iterator.hasNext()) { for (Iterator 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> The Collection Interface


Y de manera similar, si tiene un ListIterator y desea agregar elementos, puede usar ListIterator#add , por la misma razón que puede usar Iterator#remove – está diseñado para permitirlo.

Esto funciona:

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

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

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

 Collection coll = new ArrayList(); //populate coll.removeIf(i -> i.intValue() == 5); 

Dado que la pregunta ya ha sido respondida, es decir, la mejor manera es utilizar el método remove del objeto iterador, entraré en los detalles del lugar donde se produce el error "java.util.ConcurrentModificationException" .

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. Resulta que modCount se diferencia de expectedModCount , lo que da como resultado la excepción ConcurrentModificationException .

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 l = new ArrayList(); Collection itemsToRemove = new ArrayList(); 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); } 

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 haya mejores formas en Java 8, por ejemplo, removeIf o filter en las transmisiones.

La misma respuesta que Claudio con un bucle for:

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

Con Eclipse Collections (anteriormente GS Collections ), el método removeIf definido en MutableCollection funcionará:

 MutableList 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 syntax Lambda de Java 8 esto se puede escribir de la siguiente manera:

 MutableList 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 necesaria aquí porque se agregó un método removeIf predeterminado en la interfaz java.util.Collection en Java 8.

Nota: soy un committer para las colecciones de Eclipse .

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

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

Con un bucle for tradicional

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

Las personas afirman que no se puede eliminar de una Colección iterada por un bucle foreach. Solo quería señalar que es técnicamente incorrecto y describir exactamente (sé que la pregunta del OP es tan avanzada como para obviar saber esto) el código detrás de esa suposición:

  for (TouchableObj obj : untouchedSet) { // <--- This is where ConcurrentModificationException strikes if (obj.isTouched()) { untouchedSet.remove(obj); touchedSt.add(obj); break; // this is key to avoiding returning to the foreach } } 

No es que no pueda eliminar de la Colletion iterada en lugar de que no pueda continuar la iteración una vez que lo haga. De ahí la break en el código de arriba.

Disculpas si esta respuesta es un caso de uso algo especializado y más adecuado para el hilo original del que llegué aquí, ese está marcado como un duplicado (a pesar de que este hilo aparece más matizado) de esto y bloqueado.

Un ListIterator permite agregar o quitar elementos en la lista. Supongamos que tiene una lista de objetos de Car :

 List cars = ArrayList<>(); // add cars here... for (ListIterator carIterator = cars.listIterator(); carIterator.hasNext(); ) { if () { carIterator().remove() } else if () { carIterator().add(aNewCar); } } 

La mejor manera (recomendada) es el uso del paquete java.util.Concurrent. Al usar este paquete, puede evitar fácilmente esta excepción. refiera el código modificado

 public static void main(String[] args) { Collection l = new CopyOnWriteArrayList(); 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); } 

Tengo una sugerencia para el problema anterior. No es necesario tener una lista secundaria ni tiempo adicional. Encuentre un ejemplo que haría lo mismo pero de una manera diferente.

 //"list" is ArrayList //"state" is some boolean variable, which when set to true, Object will be removed from the list int index = 0; while(index < list.size()) { Object r = list.get(index); if( state ) { list.remove(index); index = 0; continue; } index += 1; } 

Esto evitaría la Excepción de Concurrencia.

En caso de que ArrayList: remove (int index) – if (index es la posición del último elemento) lo evita sin System.arraycopy() y no toma tiempo para esto.

el tiempo de la matriz aumenta si (el índice disminuye), por cierto, los elementos de la lista también disminuyen.

la mejor manera eficaz de eliminación es eliminar sus elementos en orden descendente: while(list.size()>0)list.remove(list.size()-1); // toma O (1) while(list.size()>0)list.remove(0); // toma O (factorial (n))

 //region prepare data ArrayList ints = new ArrayList(); ArrayList toRemove = new ArrayList(); Random rdm = new Random(); long millis; for (int i = 0; i < 100000; i++) { Integer integer = rdm.nextInt(); ints.add(integer); } ArrayList intsForIndex = new ArrayList(ints); ArrayList intsDescIndex = new ArrayList(ints); ArrayList intsIterator = new ArrayList(ints); //endregion // region for index millis = System.currentTimeMillis(); for (int i = 0; i < intsForIndex.size(); i++) if (intsForIndex.get(i) % 2 == 0) intsForIndex.remove(i--); System.out.println(System.currentTimeMillis() - millis); // endregion // region for index desc millis = System.currentTimeMillis(); for (int i = intsDescIndex.size() - 1; i >= 0; i--) if (intsDescIndex.get(i) % 2 == 0) intsDescIndex.remove(i); System.out.println(System.currentTimeMillis() - millis); //endregion // region iterator millis = System.currentTimeMillis(); for (Iterator iterator = intsIterator.iterator(); iterator.hasNext(); ) if (iterator.next() % 2 == 0) iterator.remove(); System.out.println(System.currentTimeMillis() - millis); //endregion 
  • para bucle de índice: 1090 mseg
  • para el índice desc: 519 mseg — la mejor
  • para el iterador: 1043 mseg

ConcurrentHashMap o ConcurrentLinkedQueue o ConcurrentSkipListMap pueden ser otra opción, ya que nunca lanzarán ninguna ConcurrentModificationException, incluso si elimina o agrega un elemento.

 for (Integer i : l) { if (i.intValue() == 5){ itemsToRemove.add(i); break; } } 

La captura es la siguiente después de eliminar el elemento de la lista si omite la llamada interna iterator.next (). ¡aún funciona! Aunque no propongo escribir código como este, me ayuda a entender el concepto detrás de él 🙂

¡Aclamaciones!

 Collection l = new ArrayList();//Do the collection thing... l.removeIf(i -> i == 5); //iterates through the collection and removes every occurence of 5 

Las expresiones Lambda y los métodos de Colección en Jdk 8 vienen en Handy y agregan un poco de azúcar sintáctico 😊.

El método removeIf recorre la colección y filtra con Predicate. Un predicado es la función de un argumento que devuelve un valor booleano … Al igual que boolean _bool = (str) -> str.equals("text");

 List strings=new ArrayList(){}; while(strings.size() > 0) { String str = strings.remove(0); } 

Puede repetir la lista usando for-loop y necesita llamar a list.remove (0). Necesita codificar el índice el parámetro de índice de eliminación con cero. Ver también esta respuesta :

 List list = new ArrayList(); list.add(1); list.add(2); list.add(3); list.add(4); int list_size = list.size(); for (int i = 0; i < list_size; i++) { list.remove(0); } 

Además de @assylias answer , también puedes usar la nueva API api si usas Java 8:

 List l = Arrays.asList(4, 5, 6); static boolean condition(Integer i) { return i == 5; } static Predicate predicate = YourClassName::condition; l.stream() .filter(predicate.negate()) .forEach(System.out::println); 

Si invierte la condición, la solución es aún más concisa ya que no necesita negate() el predicado, lo que le permite usar solo la referencia del método:

 List l = Arrays.asList(4, 5, 6); static boolean condition(Integer i) { return i != 5; // <-- condition has been negated } l.stream() .filter(YourClassName::condition) .forEach(System.out::println); 

Una de las maravillas de esto es que la transmisión se evalúa de forma diferida, es decir, la operación de filter() no se evalúa realmente hasta que es utilizada por una operación de terminal como forEach() . Se puede encontrar más sobre esto en el Tutorial de Oracle.

esta podría no ser la mejor manera, pero para la mayoría de los casos pequeños esto debería ser aceptable:

“crea una segunda matriz vacía y agrega solo las que quieras conservar”

No recuerdo dónde leí esto desde … para ser justos Voy a hacer este wiki con la esperanza de que alguien lo encuentre o simplemente para no ganar un representante que no merezco.