Pregunta Llamar a eliminar en un bucle foreach en Java [duplicar]


Esta pregunta ya tiene una respuesta aquí:

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

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

Como una adición, ¿es legal eliminar los 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));
}

516
2017-07-28 20:39


origen


Respuestas:


Para eliminar de manera 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 Documentación de Java :

Los iteradores devueltos por el iterador y listIterator de esta clase   los métodos son a prueba de fallas: si la lista se modifica estructuralmente en cualquier   tiempo después de que se crea el iterador, de cualquier manera, excepto a través del   propios métodos remove o add del iterador, el iterador lanzará un   ConcurrentModificationException. Por lo tanto, frente a la concurrencia   modificación, el iterador falla de forma rápida y limpia, en lugar de   arriesgando el comportamiento arbitrario, no determinista en un tiempo indeterminado   en el futuro.

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


780
2017-07-28 20:43



No quieres hacer eso. Puede causar un comportamiento indefinido dependiendo de la colección. Quieres usar un Iterador directamente. Aunque para cada construcción es azúcar sintáctica y realmente está utilizando 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   la colección se modifica mientras   la iteración está en progreso de cualquier manera   que no sea llamando a este método.

En cambio escribe tu 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.removeno List.remove.

Apéndice:

Incluso si está eliminando un elemento que aún no se ha iterado, aún no desea modificar la colección y luego usar el Iterator. Puede modificar la colección de una manera que es sorprendente y afecta las operaciones futuras en el Iterator.


151
2017-07-28 20:43



El diseño de Java del "bucle forzado mejorado" fue para no exponer el iterador al código, pero la única forma de eliminar un elemento de forma segura es acceder al iterador. Entonces, en este caso, tienes que hacerlo en 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 vale la pena el bucle for mejorado, entonces puede agregar los elementos a una colección temporal y llamar a removeAll en la lista después del bucle.

EDITAR (re addendum): No, cambiar la lista de cualquier 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 manera 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.


54
2017-07-28 20:47



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

Usted clona la lista names e iterar a través del clon mientras lo quita de la lista original. Un poco más limpio que la respuesta principal.


42
2018-01-23 22:01



No sabía sobre iteradores, sin embargo, esto es lo que estaba haciendo hasta hoy para eliminar elementos de una lista dentro de un ciclo:

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

Esto siempre funciona, y podría usarse en otros lenguajes o estructuras que no admitan iteradores.


24
2018-05-18 14:09



Sí, puedes usar el ciclo 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 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

19
2017-12-10 11:52



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


3
2017-07-28 23:25



Asegúrate de que esto no sea 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 implicaba un código antiguo que pasaba por una Lista usando indecies para eliminar 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;

¿Cuándo se preferiría "eliminar"? 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.


3
2017-11-14 16:57



  1. Pruebe esto 2. y cambie la condición a "INVIERNO" y 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");
  }
 }

1
2018-01-05 14:24



Es mejor usar un iterador cuando desee eliminar un elemento de una lista

porque el código fuente de eliminar es

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

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


1
2018-02-09 08:51