Llamando a remove en el bucle foreach en Java [duplicar]


Esta pregunta ya tiene una respuesta aquí:

En Java, ¿es legal llamar a remove en una colección cuando se itera a través de la colección utilizando un bucle foreach? Por ejemplo:

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

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

//Assume that the names list as duplicate entries
List<String> names = ....
for (String name : names) {
    // Do something
    while (names.remove(name));
}
Author: James McMahon, 2009-07-29

11 answers

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();
}

De la Documentación de Java :

Los iteradores devueltos por el iterador y el listIterador de esta clase los métodos son fail-fast: 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 la los propios métodos remove o add del iterador, el iterador lanzará un ConcurrentModificationException. Por lo tanto, en la cara de concurrente modificación, el iterador falla rápida y limpiamente, en lugar de arriesgarse a un comportamiento arbitrario y no determinista en un momento indeterminado en el futuro.

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

 792
Author: Mark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-02 11:11:03

No quieres hacer eso. Puede causar un comportamiento indefinido dependiendo de la colección. Desea utilizar un iterador directamente. Aunque para cada construcción es sugar sintáctico y realmente está usando un iterador, lo oculta de su código para que no pueda acceder a él para llamar Iterator.remove.

El comportamiento de un iterador es no especificado si el subyacente colección se modifica mientras que el iteración está en progreso de cualquier manera que no sea llamando a esto método.

En su lugar escriba su 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 está eliminando un elemento que aún no se ha iterado, aún no desea modificar la colección y luego usar Iterator. Podría modificar la colección de una manera que es sorprendente y afecta a las operaciones futuras en el Iterator.

 152
Author: Jared Oberhaus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-28 20:48:37

El diseño java del "enhanced for loop" era no exponer el iterador al código, pero la única manera de eliminar de forma segura un elemento es acceder al iterador. Así que en este caso tienes 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 el bucle for mejorado realmente vale la pena, entonces podría agregar los elementos a una colección temporal y llamar a removeAll en la lista después del bucle.

EDIT (re addendum): No, cambiar la lista de cualquier manera fuera del iterador.método remove () mientras iterar causará problemas. La única manera de evitar esto es usar un CopyOnWriteArrayList, pero eso está realmente pensado para problemas de concurrencia.

La forma más barata (en términos de líneas de código) de eliminar duplicados es volcar la lista en un LinkedHashSet (y luego volver a una Lista si es necesario). Esto preserva el orden de inserción mientras elimina duplicados.

 55
Author: Yishai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-18 13:31:40
for (String name : new ArrayList<String>(names)) {
    // Do something
    names.remove(nameToRemove);
}

Clona la lista names e itera a través del clon mientras elimina de la lista original. Un poco más limpio que la respuesta principal.

 44
Author: ktamlyn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-22 03:37:30

No sabía acerca de los iteradores, sin embargo esto es lo que estaba 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á funcionando, y podría usarse en otros lenguajes o estructuras que no soporten iteradores.

 24
Author: Serafeim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-27 14:53:42

Sí puede usar el bucle for-each, Para hacer eso, debe mantener una lista separada para mantener la eliminación de elementos y luego eliminar esa lista de la lista de nombres utilizando el método removeAll(),

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
 20
Author: Chathuranga Withana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-11 13:00:54

Los 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 forma segura utilizando una de las colecciones concurrentes, como ConcurrentHashMap.

 3
Author: sanity,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-28 23:25:51

Asegúrese de que esto no es 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 involucró un código antiguo que circulaba a través de una Lista usando indecies para eliminar elementos de la Lista. Quería refactorizarlo para usar el estilo foreach.

Recorrió 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 del lista.

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

Para invertir 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;

¿Cuándo se prefiere "eliminar"? Una consideración es si gien una lista grande o caro "añadir", combinado con solo unos pocos eliminado en comparación con el tamaño de la lista. Podría ser más eficiente hacer solo unas pocas eliminaciones en lugar de una gran cantidad de adiciones. Pero en mi caso la situación no merecía tal optimización.

 3
Author: bmcdonald,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-14 16:57:57
  1. Prueba este 2. y cambia la condición a "INVIERNO" y te preguntarás:
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");
  }
 }
 1
Author: Carsten,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-30 15:42:18

Es mejor usar un iterador cuando se quiere eliminar un elemento de una lista

Porque el código fuente de remove es

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

Por lo tanto ,si elimina un elemento de la lista, la lista se reestructurará ,el índice del otro elemento se cambiará, esto puede resultar en algo que desea que suceda.

 1
Author: song,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-09 08:51:44

Use

.remove() de Interator o

Use

CopyOnWriteArrayList

 -5
Author: ThmHarsh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-10 05:41:46