AFAIK, hay dos enfoques:
- Iterar sobre una copia de la colección.
- Usa el iterador de la colección real
Por ejemplo,
List<Foo> fooListCopy = new ArrayList<Foo>(fooList);
for(Foo foo : fooListCopy){
// modify actual fooList
}
y
Iterator<Foo> itr = fooList.iterator();
while(itr.hasNext()){
// modify actual fooList using itr.remove()
}
¿Hay alguna razón para preferir un enfoque sobre el otro (por ejemplo, preferir el primer enfoque por la simple razón de la legibilidad)?
java
collections
iteration
usuario1329572
fuente
fuente
while
tenía reglas de alcance diferentes quefor
fooList
es una variable de instancia y llama a un método durante el ciclo que termina llamando a otro método en la misma clase que lo hacefooList.remove(obj)
. He visto que esto suceda. En cuyo caso, copiar la lista es lo más seguro.Respuestas:
Permítanme dar algunos ejemplos con algunas alternativas para evitar a
ConcurrentModificationException
.Supongamos que tenemos la siguiente colección de libros.
Recoger y quitar
La primera técnica consiste en recopilar todos los objetos que queremos eliminar (por ejemplo, utilizando un bucle for mejorado) y después de terminar de iterar, eliminamos todos los objetos encontrados.
Esto supone que la operación que desea hacer es "eliminar".
Si desea "agregar" este enfoque también funcionaría, pero supongo que iteraría sobre una colección diferente para determinar qué elementos desea agregar a una segunda colección y luego emitir un
addAll
método al final.Usando ListIterator
Si está trabajando con listas, otra técnica consiste en utilizar una
ListIterator
que tenga soporte para eliminar y agregar elementos durante la iteración misma.Nuevamente, usé el método "eliminar" en el ejemplo anterior, que es lo que su pregunta parecía implicar, pero también puede usar su
add
método para agregar nuevos elementos durante la iteración.Usando JDK> = 8
Para aquellos que trabajan con Java 8 o versiones superiores, hay un par de otras técnicas que podría utilizar para aprovecharlo.
Podría usar el nuevo
removeIf
método en laCollection
clase base:O use la nueva API de transmisión:
En este último caso, para filtrar elementos de una colección, reasigne la referencia original a la colección filtrada (es decir
books = filtered
) o utilice la colección filtrada aremoveAll
los elementos encontrados de la colección original (es decirbooks.removeAll(filtered)
).Usar sublista o subconjunto
También hay otras alternativas. Si la lista está ordenada y desea eliminar elementos consecutivos, puede crear una sublista y luego borrarla:
Como la sublista está respaldada por la lista original, esta sería una forma eficiente de eliminar esta subcolección de elementos.
Algo similar podría lograrse con conjuntos ordenados utilizando el
NavigableSet.subSet
método, o cualquiera de los métodos de corte que se ofrecen allí.Consideraciones:
El método que utilice puede depender de lo que tenga intención de hacer.
removeAl
técnica funcionan con cualquier Colección (Colección, Lista, Conjunto, etc.).ListIterator
técnica obviamente solo funciona con listas, siempre que suListIterator
implementación dada ofrezca soporte para operaciones de agregar y quitar.Iterator
enfoque funcionaría con cualquier tipo de colección, pero solo admite operaciones de eliminación.ListIterator
/Iterator
la ventaja obvia es no tener que copiar nada, ya que eliminamos a medida que iteramos. Entonces, esto es muy eficiente.removeAll
enfoque, la desventaja es que tenemos que repetir dos veces. Primero iteramos en el bucle de búsqueda buscando un objeto que coincida con nuestros criterios de eliminación, y una vez que lo hemos encontrado, pedimos eliminarlo de la colección original, lo que implicaría un segundo trabajo de iteración para buscar este artículo para eliminarloIterator
interfaz está marcado como "opcional" en Javadocs, lo que significa que podría haberIterator
implementaciones que arrojenUnsupportedOperationException
si invocamos el método remove. Como tal, diría que este enfoque es menos seguro que otros si no podemos garantizar el soporte del iterador para la eliminación de elementos.fuente
removeAll(filtered)
. Un acceso directo para que seríaremoveIf(b -> b.getIsbn().equals(other))
En Java 8, hay otro enfoque. Colección # removeIf
p.ej:
fuente
El primer enfoque funcionará, pero tiene la sobrecarga obvia de copiar la lista.
El segundo enfoque no funcionará porque muchos contenedores no permiten modificaciones durante la iteración. Esto incluye
ArrayList
.Si la única modificación es eliminar el elemento actual, puede hacer que el segundo enfoque de trabajo mediante el uso
itr.remove()
(es decir, utilizar el iterador 'sremove()
método, no el contenedor ' s). Este sería mi método preferido para iteradores compatiblesremove()
.fuente
Iterator
interfaz está marcado como opcional en Javadocs, lo que significa que podría haber implementaciones de Iterator que podrían arrojarseUnsupportedOperationException
. Como tal, diría que este enfoque es menos seguro que el primero. Dependiendo de las implementaciones destinadas a ser utilizadas, el primer enfoque podría ser más adecuado.remove()
en la colección original también puede lanzarUnsupportedOperationException
: docs.oracle.com/javase/7/docs/api/java/util/… . Lamentablemente, las interfaces de contenedor Java se definen como extremadamente poco confiables (honestamente, derrotando el punto de la interfaz). Si no conoce la implementación exacta que se utilizará en tiempo de ejecución, es mejor hacer las cosas de una manera inmutable; por ejemplo, use la API Java 8+ Streams para filtrar los elementos y recopilarlos en un nuevo contenedor, luego Reemplace completamente el viejo con él.Solo el segundo enfoque funcionará. Puede modificar la colección durante la iteración usando
iterator.remove()
solo. Todos los demás intentos causaránConcurrentModificationException
.fuente
Antiguo temporizador favorito (todavía funciona):
fuente
No puede hacer el segundo, porque incluso si usa el
remove()
método en Iterator , obtendrá una excepción .Personalmente, preferiría el primero para todas las
Collection
instancias, a pesar de la escucha adicional de crear el nuevoCollection
, encuentro que es menos propenso a errores durante la edición por otros desarrolladores. En algunas implementaciones de Collection, Iteratorremove()
es compatible, en otras no. Puedes leer más en los documentos de Iterator .La tercera alternativa es crear una nueva
Collection
, iterar sobre la original y agregar todos los miembros de la primeraCollection
a la segundaCollection
que no estén disponibles para su eliminación. Dependiendo del tamañoCollection
y la cantidad de eliminaciones, esto podría ahorrar significativamente en memoria, en comparación con el primer enfoque.fuente
Elegiría el segundo ya que no tienes que hacer una copia de la memoria y el iterador funciona más rápido. Así ahorras memoria y tiempo.
fuente
¿Por qué no esto?
Y si es un mapa, no una lista, puede usar keyset ()
fuente
get(i)
debe visitar todos los nodos hasta llegari
.Foo.remove(i);
que debes haceri--;
?