java - imprimir - Eliminar elementos de la colección mientras se itera
imprimir linkedlist java (8)
¿Hay alguna razón para preferir un enfoque sobre el otro
El primer enfoque funcionará, pero tiene la carga 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 funcione utilizando itr.remove()
(es decir, use el método remove()
del iterador , no el del contenedor ). Este sería mi método preferido para los iteradores que admiten remove()
.
AFAIK, hay dos enfoques:
- Iterato sobre una copia de la colección
- Use el iterador de la colección actual
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 el simple motivo de legibilidad)?
En Java 8, hay otro enfoque. Collection#removeIf
p.ej:
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.removeIf(i -> i > 2);
No puede hacer el segundo, porque incluso si usa el método remove()
en Iterator , obtendrá una Excepción lanzada .
Personalmente, preferiría la primera para todas las instancias de la Collection
, a pesar de la escucha adicional de crear la nueva Collection
, creo que es menos propenso a errores durante la edición de otros desarrolladores. En algunas implementaciones de Colección, Iterator remove()
es compatible, en otras no lo es. Puede leer más en los documentos para Iterator .
La tercera alternativa es crear una nueva Collection
, iterar sobre la original y agregar todos los miembros de la primera Collection
a la segunda Collection
que no están listos para su eliminación. Dependiendo del tamaño de la Collection
y del número de eliminaciones, esto podría ahorrar significativamente en la memoria, en comparación con el primer enfoque.
Permítanme dar algunos ejemplos con algunas alternativas para evitar una ConcurrentModificationException
.
Supongamos que tenemos la siguiente colección de libros
List<Book> books = new ArrayList<Book>();
books.add(new Book(new ISBN("0-201-63361-2")));
books.add(new Book(new ISBN("0-201-63361-3")));
books.add(new Book(new ISBN("0-201-63361-4")));
Recoge y quita
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.
ISBN isbn = new ISBN("0-201-63361-2");
List<Book> found = new ArrayList<Book>();
for(Book book : books){
if(book.getIsbn().equals(isbn)){
found.add(book);
}
}
books.removeAll(found);
Esto supone que la operación que desea hacer es "eliminar".
Si desea "agregar", este enfoque también funcionaría, pero supongo que iterará sobre una colección diferente para determinar qué elementos desea agregar a una segunda colección y luego emitirá un método addAll
al final.
Usando ListIterator
Si está trabajando con listas, otra técnica consiste en usar un ListIterator
que tiene soporte para eliminar y agregar elementos durante la iteración misma.
ListIterator<Book> iter = books.listIterator();
while(iter.hasNext()){
if(iter.next().getIsbn().equals(isbn)){
iter.remove();
}
}
De nuevo, utilicé el método "eliminar" en el ejemplo anterior, que es lo que su pregunta parecía implicar, pero también puede usar su método de agregar 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 puede utilizar para aprovecharlo.
Puede usar el nuevo método removeIf
en la clase base de Collection
:
ISBN other = new ISBN("0-201-63361-2");
books.removeIf(b -> b.getIsbn().equals(other));
O use la nueva API de transmisión:
ISBN other = new ISBN("0-201-63361-2");
List<Book> filtered = books.stream()
.filter(b -> b.getIsbn().equals(other))
.collect(Collectors.toList());
En este último caso, para filtrar elementos de una colección, reasigna la referencia original a la colección filtrada (es decir, books = filtered
) o utiliza la colección filtrada para removeAll
los elementos encontrados de la colección original (es decir, books.removeAll(filtered)
)
Usar Sublist o Subset
También hay otras alternativas. Si la lista está ordenada y desea eliminar elementos consecutivos, puede crear una sublista y luego borrarla:
books.subList(0,5).clear();
Como la sublista está respaldada por la lista original, esta sería una forma eficiente de eliminar esta subcolección de elementos.
Se podría lograr algo similar con los conjuntos ordenados que usan el método NavigableSet.subSet
o cualquiera de los métodos de corte ofrecidos allí.
Consideraciones:
El método que use puede depender de lo que se propone hacer
- La técnica de recopilación y
removeAl
funciona con cualquier colección (colección, lista, conjunto, etc.). - La técnica
ListIterator
obviamente solo funciona con listas, siempre que su implementación dada deListIterator
ofrezca soporte para operaciones de agregar y quitar. - El enfoque
Iterator
funcionaría con cualquier tipo de colección, pero solo admite operaciones de eliminación. - Con el enfoque
ListIterator
/ListIterator
, la ventaja obvia es no tener que copiar nada, ya que lo eliminamos al iterar. Entonces, esto es muy eficiente. - El ejemplo de las corrientes JDK 8 en realidad no eliminó nada, pero buscó los elementos deseados, luego reemplazamos la referencia de la colección original con la nueva y dejamos que la anterior sea recolectada. Entonces, iteramos solo una vez sobre la colección y eso sería eficiente.
- En el método de recopilación y
removeAll
, la desventaja es que debemos iterar dos veces. Primero iteramos en el foor-loop buscando un objeto que coincida con nuestros criterios de eliminación, y una vez que lo hemos encontrado, le pedimos que lo elimine de la colección original, lo que implicaría una segunda iteración para buscar este elemento con el fin de eliminarlo - Creo que vale la pena mencionar que el método remove de la interfaz
Iterator
está marcado como "opcional" en Javadocs, lo que significa que podría haber implementacionesIterator
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.
Solo el segundo enfoque funcionará. Puede modificar la recopilación durante la iteración utilizando solo iterator.remove()
. Todos los demás intentos causarán ConcurrentModificationException
.
También hay una solución simple para "iterar" una Collection
y eliminar cada elemento.
List<String> list = new ArrayList<>();
//Fill the list
Simplemente conciste en el bucle hasta que la lista esté vacía, y en cada iteración, eliminamos el primer elemento con remove(0)
.
while(!list.isEmpty()){
String s = list.remove(0);
// do you thing
}
No creo que esto tenga ninguna mejora en comparación con el Iterator
, todavía requiere tener una lista mutable, pero me gusta la simplicidad de esta solución.
Yo elegiría el segundo ya que no tiene que hacer una copia de la memoria y el iterador funciona más rápido. Así que ahorras memoria y tiempo.
por qué no esto?
for( int i = 0; i < Foo.size(); i++ )
{
if( Foo.get(i).equals( some test ) )
{
Foo.remove(i);
}
}
Y si es un mapa, no una lista, puede usar el conjunto de claves ()