web-dev-qa-db-fra.com

Appelant remove in foreach loop dans Java

En Java, est-il légal d'appeler remove sur une collection lors d'une itération dans la collection à l'aide d'une boucle foreach? Par exemple:

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

En tant qu’additif, est-il légal de supprimer des éléments qui n’ont pas encore été réitérés? Par exemple,

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

Pour supprimer en toute sécurité une collection tout en effectuant une itération, utilisez un itérateur.

Par exemple:

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 documentation Java :

Les itérateurs retournés par les méthodes iterator et listIterator de cette classe sont ultra-rapides: si la liste est structurellement modifiée à tout moment après la création de l'itérateur, de quelque manière que ce soit, sauf par le biais de ses propres méthodes remove et add, l'itérateur lève une exception ConcurrentModificationException. Ainsi, face à une modification concurrente, l'itérateur échoue rapidement et proprement, au lieu de risquer un comportement non déterministe arbitraire à une date future indéterminée.

Ce qui n’est peut-être pas clair pour beaucoup de novices, c’est que le fait de parcourir une liste à l’aide des constructions for/foreach crée implicitement un itérateur qui est nécessairement inaccessible. Cette information peut être trouvée ici

855
Mark

Tu ne veux pas faire ça. Cela peut entraîner un comportement indéfini selon la collection. Vous voulez utiliser un Iterator directement. Bien que le pour chaque construction soit un sucre syntaxique et utilise vraiment un itérateur, il le cache de votre code afin que vous ne puissiez pas y accéder pour appeler Iterator.remove .

Le comportement d'un itérateur n'est pas spécifié si la collection sous-jacente est modifiée pendant que l'itération est en cours autrement qu'en appelant cette méthode.

Au lieu d'écrire votre code:

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

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

Notez que le code appelle _Iterator.remove_, pas _List.remove_.

Addendum:

Même si vous supprimez un élément qui n'a pas encore été itéré, vous ne souhaitez toujours pas modifier la collection, puis utilisez le Iterator. Cela pourrait modifier la collection de manière surprenante et affecter les opérations futures sur le Iterator.

156
Jared Oberhaus

La conception Java de la boucle "améliorée pour" était de ne pas exposer l'itérateur au code, mais le seul moyen de supprimer un élément en toute sécurité consiste à accéder à l'itérateur. Donc, dans ce cas, vous devez le faire à l'ancienne:

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

Si, dans le code réel, la boucle for améliorée en vaut vraiment la peine, vous pouvez ajouter les éléments à une collection temporaire et appeler removeAll sur la liste après la boucle.

EDIT (addendum): Non, modifier la liste de quelque manière que ce soit en dehors de la méthode iterator.remove () en itérant causera des problèmes. La seule solution consiste à utiliser une CopyOnWriteArrayList, mais cela est vraiment destiné aux problèmes de simultanéité.

Le moyen le moins coûteux (en termes de lignes de code) de supprimer les doublons est de vider la liste dans un LinkedHashSet (puis de la renvoyer dans une liste si vous en avez besoin). Cela préserve l'ordre d'insertion tout en supprimant les doublons.

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

Vous clonez la liste names et parcourez le clone tout en supprimant de la liste d'origine. Un peu plus propre que la réponse du haut.

54
ktamlyn

Je ne connaissais pas les itérateurs, mais voici ce que je faisais jusqu'à aujourd'hui pour supprimer des éléments d'une liste dans une boucle:

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

Cela fonctionne toujours et pourrait être utilisé dans d'autres langages ou structures ne supportant pas les itérateurs.

26
Serafeim

Oui, vous pouvez utiliser la boucle for-each. Pour ce faire, vous devez gérer une liste distincte contenant les éléments à supprimer, puis supprimer cette liste de la liste des noms à l'aide de la méthode 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
21
Chathuranga Withana

Ceux qui disent que vous ne pouvez pas supprimer en toute sécurité un élément d'une collection, sauf par le biais de l'Iterator, ne sont pas tout à fait corrects. Vous pouvez le faire en toute sécurité en utilisant l'une des collections simultanées telle que ConcurrentHashMap.

4
sanity

Assurez-vous qu'il ne s'agit pas d'une odeur de code. Est-il possible d'inverser la logique et d'être "inclusif" plutôt qu'exclusif?

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

La situation qui m'a conduit à cette page impliquait un ancien code qui parcourait une liste en utilisant indécies pour supprimer des éléments de la liste. Je voulais le refactoriser pour utiliser le style foreach.

Il a parcouru toute une liste d'éléments pour vérifier ceux auxquels l'utilisateur était autorisé à accéder et a supprimé ceux de cette liste.

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

Pour inverser cela et ne pas utiliser la suppression:

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

Quand "enlever" serait-il préféré? Une considération est si gien une longue liste ou cher "ajouter", combiné avec seulement quelques-uns enlevés par rapport à la taille de la liste. Il serait peut-être plus efficace de ne faire que quelques suppressions plutôt que de nombreux ajouts. Mais dans mon cas, la situation ne méritait pas une telle optimisation.

3
bmcdonald
  1. Essayez ceci 2. et changez la condition en "HIVER" et vous vous demanderez:
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
Carsten

Il est préférable d'utiliser un itérateur lorsque vous souhaitez supprimer un élément d'une liste.

parce que le code source de supprimer est

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

ainsi, si vous supprimez un élément de la liste, la liste sera restructurée, l'index de l'autre élément sera modifié, il peut en résulter quelque chose que vous souhaitez voir se produire.

1
song