Todos sabemos que no puede hacer lo siguiente por ConcurrentModificationException
:
for (Object i : l) {
if (condition(i)) {
l.remove(i);
}
}
Pero 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<>();
for (int i = 0; i < 10; ++i) {
l.add(4);
l.add(5);
l.add(6);
}
for (int i : l) {
if (i == 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 múltiples hilos no lo están haciendo. De todas formas.
¿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
, por lo que no puede confiar get
.
java
collections
iteration
Claudiu
fuente
fuente
Respuestas:
Iterator.remove()
es seguro, puedes usarlo así: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 alguna otra manera mientras la iteración está en progreso.Fuente: docs.oracle> La interfaz de la colección
Y de manera similar, si tiene un
ListIterator
y desea agregar elementos, puede usarListIterator#add
, por la misma razón que puedeIterator#remove
hacerlo: está diseñado para permitirlo.En su caso se trató de eliminar de una lista, pero la misma restricción se aplica si intentara
put
en unMap
mientras que la iteración de su contenido.fuente
iterator.next()
llamada en el ciclo for? Si no, ¿alguien puede explicar por qué?List.add
es "opcional" en ese mismo sentido también, pero no diría que es "inseguro" agregar a una lista.Esto funciona:
Supuse que, dado que un bucle foreach es azúcar sintáctico para iterar, usar un iterador no ayudaría ... pero le brinda esta
.remove()
funcionalidad.fuente
Con Java 8 puede usar el nuevo
removeIf
método . Aplicado a tu ejemplo:fuente
removeIf
utilizaIterator
ywhile
bucle. Puedes verlo en java 8java.util.Collection.java
ArrayList
anularlo por razones de rendimiento. Al que se refiere es solo la implementación predeterminada.equals
no se usa en absoluto, por lo que no tiene que implementarse. (Pero, por supuesto, si lo usaequals
en su prueba, entonces debe implementarse de la manera que lo desee.)Dado que la pregunta ya ha sido respondida, es decir, la mejor manera es usar el método remove del objeto iterador, entraría en los detalles del lugar donde
"java.util.ConcurrentModificationException"
se arroja el error .Cada clase de colección tiene una clase privada que implementa la interfaz Iterator y proporciona métodos como
next()
,remove()
yhasNext()
.El código para el siguiente se parece a esto ...
Aquí el método
checkForComodification
se implementa comoEntonces, como puede ver, si intenta explícitamente eliminar un elemento de la colección. Resulta
modCount
diferente deexpectedModCount
, lo que resulta en la excepciónConcurrentModificationException
.fuente
Puede usar el iterador directamente como lo mencionó, o bien mantener una segunda colección y agregar cada elemento que desea eliminar a la nueva colección, luego eliminar todo al final. Esto le permite seguir usando la seguridad de tipo del bucle para cada uno a costa de un mayor uso de memoria y tiempo de CPU (no debería ser un gran problema a menos que tenga listas realmente grandes o una computadora realmente vieja)
fuente
En tales casos, un truco común es (¿era?) Ir hacia atrás:
Dicho esto, estoy más que feliz de que tenga mejores formas en Java 8, por ejemplo,
removeIf
ofilter
en las transmisiones.fuente
ArrayList
colecciones similares.for(int i = l.size(); i-->0;) {
?Misma respuesta que Claudio con un bucle for:
fuente
Con Eclipse Collections , el método
removeIf
definido en MutableCollection funcionará:Con la sintaxis Lambda de Java 8, esto se puede escribir de la siguiente manera:
La llamada a
Predicates.cast()
es necesaria aquí porqueremoveIf
se agregó un método predeterminado en lajava.util.Collection
interfaz en Java 8.Nota: Soy un committer para Eclipse Collections .
fuente
Haga una copia de la lista existente e itere sobre una nueva copia.
fuente
Las personas afirman que no se puede eliminar de una Colección que se repite 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 evitar saber esto) el código detrás de esa suposición:
No es que no pueda eliminar de la
Colletion
iteración, sino que no puede continuar la iteración una vez que lo hace. Por lo tanto, labreak
en el código anterior.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 duplicado (a pesar de que este hilo parece más matizado) de esto y bloqueado.
fuente
Con un bucle tradicional
fuente
i++
en la protección del bucle en lugar de hacerlo dentro del cuerpo del bucle.i++
incremento no fuera condicional - Ahora veo que es por eso que lo haces en el cuerpo :)A le
ListIterator
permite agregar o eliminar elementos en la lista. Supongamos que tiene una lista deCar
objetos:fuente
previous
método.Tengo una sugerencia para el problema anterior. No necesita lista secundaria ni tiempo extra. Encuentre un ejemplo que haga lo mismo pero de manera diferente.
Esto evitaría la excepción de concurrencia.
fuente
ArrayList
y, por lo tanto, no puede confiar en élget()
. Sin embargo, probablemente sea un buen enfoque.Collection
-Collection
no incluyeget
. (Aunque laList
interfaz FWIW incluye 'get').while
-looping aList
. Pero +1 para esta respuesta porque vino primero.ConcurrentHashMap o ConcurrentLinkedQueue o ConcurrentSkipListMap puede ser otra opción, porque nunca arrojarán ninguna ConcurrentModificationException, incluso si elimina o agrega un elemento.
fuente
java.util.concurrent
paquete. Algunas otras clases de casos de uso común / similares de ese paquete sonCopyOnWriteArrayList
&CopyOnWriteArraySet
[pero no se limitan a esas].ConcurrentModificationException
, usarlos en un bucle mejorado todavía puede causar problemas de indexación (es decir: omitir elementos oIndexOutOfBoundsException
...)Sé que esta pregunta es demasiado antigua para ser sobre Java 8, pero para aquellos que usan Java 8, pueden usar removeIf () fácilmente:
fuente
Otra forma es crear una copia de su arrayList:
fuente
i
no es unindex
sino el objeto. Tal vez llamarloobj
sería más apropiado.fuente
En el caso de ArrayList: remove (int index) - if (index es la posición del último elemento) 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 efectiva de eliminar 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))fuente
El truco es 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, ayuda a entender el concepto detrás de él :-)
¡Salud!
fuente
Ejemplo de modificación de colección segura para subprocesos:
fuente
Sé que esta pregunta supone solo una
Collection
, y no más específicamente ningunaList
. Pero para aquellos que leen esta pregunta que realmente están trabajando con unaList
referencia, puede evitarConcurrentModificationException
con unwhile
bucle (mientras modifica dentro de ella) si quiere evitarloIterator
(ya sea si desea evitarlo en general o evitarlo específicamente para lograr un orden de bucle diferente de principio a fin que se detiene en cada elemento [que creo que es el único orden queIterator
puede hacer]):* Actualización: vea los comentarios a continuación que aclaran que lo análogo también se puede lograr con el tradicional para bucle.
Sin ConcurrentModificationException de ese código.
Allí vemos que el bucle no comienza desde el principio y no se detiene en cada elemento (lo que creo
Iterator
que no puede hacer).FWIW también vemos que
get
se llamalist
, lo que no se podría hacer si su referencia fuera soloCollection
(en lugar delList
tipo más específico deCollection
): laList
interfaz incluyeget
, pero laCollection
interfaz no. Si no fuera por esa diferencia, entonces lalist
referencia podría ser unaCollection
[y, por lo tanto, técnicamente esta respuesta sería una respuesta directa, en lugar de una respuesta tangencial].El mismo código FWIWW sigue funcionando después de la modificación para comenzar al principio en cada elemento (al igual que el
Iterator
orden):fuente
ConcurrentModificationException
, pero no el bucle tradicional para el bucle (que usa la otra respuesta), sin darme cuenta de que antes es por eso que estaba motivado para escribir esta respuesta (erróneamente pensó entonces que era todos los bucles para-que emitir la excepción).Una solución podría ser rotar la lista y eliminar el primer elemento para evitar la ConcurrentModificationException o IndexOutOfBoundsException
fuente
Pruebe este (elimina todos los elementos de la lista que sean iguales
i
):fuente
también puedes usar Recursion
La recursión en Java es un proceso en el cual un método se llama a sí mismo continuamente. Un método en Java que se llama a sí mismo se llama método recursivo.
fuente
esta podría no ser la mejor manera, pero para la mayoría de los casos pequeños esto debería ser aceptable:
No recuerdo de dónde leí esto ... para ser justos, haré este wiki con la esperanza de que alguien lo encuentre o simplemente para no ganar reputación que no merezco.
fuente