2009-07-28 27 views
488

En Java, ¿es legal llamar a eliminar en una colección al iterar a través de la colección mediante un bucle foreach? Por ejemplo:Llamando eliminar en un bucle foreach en Java

List<String> names = .... 
for (String name : names) { 
    // Do something 
    names.remove(name). 
} 

Como una adición, ¿es legal eliminar los elementos que no se han repetido aún? Por ejemplo,

//Assume that the names list as duplicate entries 
List<String> names = .... 
for (String name : names) { 
    // Do something 
    while (names.remove(name)); 
} 
+29

No es un gran plan. El hecho de que el lenguaje lo tolera en cualquier ejecución determinada no lo convierte en una buena idea. –

+0

Debe haberme pillado de mal humor, pero me parece que la respuesta a esta pregunta viene directamente de la documentación de foreach. – CurtainDog

+1

http://stackoverflow.com/questions/223918/equivalent-equivalent-for-removing-elements-while-iterating-the-collection – Jeevi

Respuesta

730

Para eliminar de forma segura de una colección mientras se itera sobre ella, debe usar un iterador.

Por ejemplo:

List<String> names = .... 
Iterator<String> i = names.iterator(); 
while (i.hasNext()) { 
    String s = i.next(); // must be called before you can call i.remove() 
    // Do something 
    i.remove(); 
} 

Desde el Java Documentation:

Los iteradores devueltos por el iterador de esta clase y ListIterator métodos son a prueba de rápida: si la lista se modifica estructuralmente en cualquier tiempo 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 emitirá una ConcurrentModificationException. Por lo tanto, frente a la modificación concurrente de , el iterador falla rápida y limpiamente, en lugar de arriesgando el comportamiento no determinista arbitrario en un tiempo indeterminado en el futuro.

Quizás lo que no está claro para muchos principiantes es el hecho de que la iteración sobre una lista que utiliza las construcciones for/foreach crea implícitamente un iterador que es necesariamente inaccesible. Esta información se puede encontrar here

+32

Tenga en cuenta que debe llamar a i.next() para poder llamar a i.remove(): [docs.oracle.com/javase/6/docs/api/java/util/Iterator.html](http:// docs.oracle.com/javase/6/docs/api/java/util/Iterator.html#remove()) –

+10

Tengo curiosidad, ¿por qué se considera esto seguro? ¿El 'Iterator' actúa como intermediario? –

+0

Yo soy la segunda pregunta de James. ¿Qué tal ese método es mejor? –

147

No desea hacer eso. Puede causar un comportamiento indefinido dependiendo de la colección. Desea usar un Iterator directamente. Aunque el para cada construcción es el azúcar sintáctica y está realmente utilizando un iterador, que lo oculta a su código de manera que no se puede acceder a él a llamar Iterator.remove.

El comportamiento de un iterador es sin especificar si se modifica la colección subyacente, mientras que el iteración en curso en cualquier forma que no sea llamando a este método.

su lugar escribir el código:

List<String> names = .... 
Iterator<String> it = names.iterator(); 
while (it.hasNext()) { 

    String name = it.next(); 
    // Do something 
    it.remove(); 
} 

Tenga en cuenta que el código llama Iterator.remove, no List.remove.

Adición:

Incluso si va a quitar un elemento que no se ha reiterado terminado todavía, aún no quiere modificar la colección y luego usar el Iterator. Puede modificar la colección de una manera que sea sorprendente y afecte las operaciones futuras en el Iterator.

51

El diseño java del "bucle forzado mejorado" era para no exponer el iterador al código, pero la única manera de eliminar un elemento de forma segura es acceder al iterador.Así que en este caso hay que hacerlo de la vieja escuela:

for(Iterator<String> i = names.iterator(); i.hasNext();) { 
     String name = i.next(); 
     //Do Something 
     i.remove(); 
} 

Si en el código real del ciclo mejorado es realmente vale la pena, entonces habría que agregar los elementos a una colección temporal y llamar removeAll en la lista después el lazo.

EDITAR (re adende): No, cambiar la lista de ninguna manera fuera del método iterator.remove() mientras se itera causará problemas. La única forma de evitar esto es usar un CopyOnWriteArrayList, pero eso está realmente destinado a problemas de concurrencia.

La forma más económica (en términos de líneas de código) para eliminar duplicados es volcar la lista en un LinkedHashSet (y luego volver a una lista si es necesario). Esto conserva el orden de inserción al eliminar duplicados.

3

Aquellos que dicen que no se puede eliminar de forma segura un elemento de una colección, excepto a través del iterador, no son del todo correctos, puede hacerlo de manera segura usando una de las colecciones concurrentes como ConcurrentHashMap.

17

Sí se puede usar para-cada bucle, Para hacer eso hay que mantener una lista separada para mantener la eliminación de elementos y luego eliminar esa lista de la lista de nombres usando removeAll() método,

List<String> names = .... 

// introduce a separate list to hold removing items 
List<String> toRemove= new ArrayList<String>(); 

for (String name : names) { 
    // Do something: perform conditional checks 
    toRemove.add(name); 
}  
names.removeAll(toRemove); 

// now names list holds expected values 
+0

¿Por qué agregar la sobrecarga de otra lista? –

+1

Dado que el bucle 'for-each' oculta el' iterator', no puede llamar directamente a 'remove()'. Por lo tanto, para eliminar elementos de un bucle 'for-each' mientras se itera a través de él, debe mantener una lista separada. Esa lista mantendrá referencias a los elementos que se eliminarán ... –

+1

Es muy lento. removeAll 1. runs contains() en cada elemento toRemove 2.luego ejecuta remove(), que tiene que encontrar el índice del elemento, por lo que es necesario revisar nuevamente toda la lista. – mmatloka

1
  1. 2. Prueba esto y cambiar la condición de "invierno" y usted se preguntará:
public static void main(String[] args) { 
    Season.add("Frühling"); 
    Season.add("Sommer"); 
    Season.add("Herbst"); 
    Season.add("WINTER"); 
    for (String s : Season) { 
    if(!s.equals("Sommer")) { 
    System.out.println(s); 
    continue; 
    } 
    Season.remove("Frühling"); 
    } 
} 
23

yo no sabía nada de iteradores, sin embargo esto es lo que era haciendo hasta hoy para eliminar elementos de una lista dentro de un bucle:

List<String> names = .... 
for (i=names.size()-1;i>=0;i--) {  
    // Do something  
    names.remove(i); 
} 

Esto siempre está trabajando, y podría ser utilizado en otros idiomas o no iteradores estructuras de soporte.

+2

Como una nota al margen, debería funcionar bien con cualquier lista de clase base, pero no sería portátil para estructuras más esotéricas (como una secuencia de auto clasificación, por ejemplo, en general, en cualquier lugar, el ordinal de una entrada determinada podría cambiar entre iteraciones de lista). –

+4

Nota: esto funciona precisamente porque está iterando contraseñas sobre los elementos, por lo que los índices de los demás elementos no cambian cuando elimina el elemento 'i'th. Si estuvieras pasando de 0 a 'size() - 1' y eliminando, verías los problemas mencionados en otras respuestas. Buen trabajo, sin embargo! –

+0

Esta es una forma horrible de hacerlo. Muchas cosas pueden salir mal dependiendo de la implementación de la Lista (o Conjunto, etc.). – DavidR

3

Asegúrate de que esto no sea un olor a código. ¿Es posible invertir la lógica y ser "inclusivo" en lugar de "exclusivo"?

List<String> names = .... 
List<String> reducedNames = .... 
for (String name : names) { 
    // Do something 
    if (conditionToIncludeMet) 
     reducedNames.add(name); 
} 
return reducedNames; 

La situación que me llevó a esta página de códigos de edad involucrados que bucle a través de una lista usando indecies para eliminar los elementos de la lista. Quería refactorizarlo para usar el estilo foreach.

Pasó por una lista completa de elementos para verificar a cuáles tenía permiso de acceso el usuario, y eliminó los que no tenían permiso de la lista.

List<Service> services = ... 
for (int i=0; i<services.size(); i++) { 
    if (!isServicePermitted(user, services.get(i))) 
     services.remove(i); 
} 

Para revertir esto y no usar el remove:

List<Service> services = ... 
List<Service> permittedServices = ... 
for (Service service:services) { 
    if (isServicePermitted(user, service)) 
     permittedServices.add(service); 
} 
return permittedServices; 

Cuando se "eliminar" debe preferirse? Una consideración es si hay una gran lista o un "agregado" caro, combinado con solo unos pocos eliminados en comparación con el tamaño de la lista. Puede ser más eficiente hacer solo algunas eliminaciones en lugar de muchas otras. Pero en mi caso, la situación no merecía tal optimización.

1

Es mejor utilizar un iterador cuando se desea eliminar el elemento de una lista

porque el código fuente de quitar es

if (numMoved > 0) 
    System.arraycopy(elementData, index+1, elementData, index, 
      numMoved); 
elementData[--size] = null; 

así, si se elimina un elemento de la lista, la lista será reestructurado, el índice del otro elemento cambiará, esto puede dar como resultado algo que desea que ocurra.

41
for (String name : new ArrayList<String>(names)) { 
    // Do something 
    names.remove(nameToRemove); 
} 

clonar la lista names y una iteración en el clon mientras se quita de la lista original. Un poco más limpio que la respuesta principal.

+3

se supone que esta es la primera respuesta aquí ... – itzhar

+3

Ten cuidado. Para objetos compuestos sin métodos equals y hashCode, podría fallar. Sin embargo, la respuesta marcada la elimina con seguridad. – D3V

+4

Aunque corto y limpio, si el rendimiento/uso de la memoria es un problema, vale la pena señalar que esta solución se ejecuta en O (n²) y crea una copia de la lista original, que requiere memoria y una operación según el tipo de lista. Al usar un iterador sobre una lista enlazada, puede reducir la complejidad a O (n). – SND

-4

Uso

.Remove() de Interator o

Uso

CopyOnWriteArrayList

Cuestiones relacionadas