Question Appeler remove dans foreach loop dans Java [duplicate]


Cette question a déjà une réponse ici:

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

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

En tant qu'addendum, est-il légal de supprimer des éléments qui n'ont pas encore été itératifs? Par exemple,

//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


origine


Réponses:


Pour retirer en toute sécurité d'une collection tout en itérant au-dessus, vous devez utiliser un Iterator.

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

Du Documentation Java :

Les itérateurs retournés par l'itérateur et le listIterator de cette classe   les méthodes sont fail-fast: si la liste est structurellement modifiée à tout   après la création de l'itérateur, de quelque manière que ce soit, sauf   propre supprimer ou ajouter des méthodes de l'itérateur, l'itérateur lancera un   ConcurrentModificationException. Ainsi, face à des concurrents   modification, l'itérateur échoue rapidement et proprement, plutôt que   risquer un comportement arbitraire, non déterministe à un moment indéterminé   A l'avenir.

Peut-être que ce qui n'est pas clair pour beaucoup de novices est le fait que l'itération sur une liste en utilisant les constructions for / foreach crée implicitement un itérateur qui est nécessairement inaccessible. Cette information peut être trouvée ici


780
2017-07-28 20:43



Tu ne veux pas faire ça. Cela peut entraîner un comportement indéfini en fonction de la collection. Vous voulez utiliser un Iterator directement. Bien que le pour chaque construction soit du sucre syntaxique et utilise réellement un itérateur, il le cache à partir de votre code de sorte que vous ne pouvez pas y accéder pour appeler Iterator.remove.

Le comportement d'un itérateur est   non spécifié si le sous-jacent   la collection est modifiée pendant que le   l'itération est en cours de quelque manière que ce soit   autrement qu'en appelant cette méthode.

Écrivez plutôt votre code:

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

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

Notez que les appels de code Iterator.remove, ne pas List.remove.

Addenda:

Même si vous supprimez un élément qui n'a pas encore été répété, vous ne voulez toujours pas modifier la collection, puis utiliser le Iterator. Il pourrait modifier la collection d'une manière surprenante et affecter les opérations futures sur le Iterator.


151
2017-07-28 20:43



La conception java de la «boucle for enhanced» consistait à ne pas exposer l'itérateur au code, mais la seule façon de retirer un élément en toute sécurité est d'accéder à l'itérateur. Donc, dans ce cas, vous devez le faire old school:

 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 enhanced vaut vraiment la peine, vous pouvez ajouter les éléments à une collection temporaire et appeler removeAll sur la liste après la boucle.

EDIT (re addendum): Non, changer la liste de quelque façon que ce soit en dehors de la méthode iterator.remove () pendant l'itération causera des problèmes. La seule façon de contourner cela est d'utiliser un CopyOnWriteArrayList, mais c'est vraiment prévu pour les problèmes de concurrence.

Le moyen le moins cher (en termes de lignes de code) pour supprimer les doublons est de vider la liste dans un LinkedHashSet (et ensuite de nouveau dans une liste si vous en avez besoin). Cela préserve l'ordre d'insertion tout en supprimant les doublons.


54
2017-07-28 20:47



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

Vous clonez la liste names et parcourez le clone pendant que vous supprimez de la liste d'origine. Un peu plus propre que la meilleure réponse.


42
2018-01-23 22:01



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 prenant pas en charge les itérateurs.


24
2018-05-18 14:09



Oui, vous pouvez utiliser la boucle for-each, Pour ce faire, vous devez gérer une liste séparée pour conserver les éléments, puis supprimer cette liste de la liste des noms en utilisant removeAll() méthode,

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



Ceux qui disent que vous ne pouvez pas supprimer en toute sécurité un élément d'une collection, sauf via l'Iterator, ne sont pas tout à fait corrects, vous pouvez le faire en toute sécurité en utilisant l'une des collections concurrentes telles que ConcurrentHashMap.


3
2017-07-28 23:25



Assurez-vous que ce n'est pas une odeur de code. Est-il possible d'inverser la logique et d'être «inclusif» plutôt que «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 faisait une boucle dans une liste en utilisant indecies pour supprimer des éléments de la liste. Je voulais le refactoriser pour utiliser le style foreach.

Il a bouclé une liste complète d'éléments pour vérifier ceux auxquels l'utilisateur avait l'autorisation d'accéder, et a supprimé ceux qui n'avaient pas l'autorisation de la 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 "supprimer" serait-il préférable? Une considération est si gien une grande liste ou cher "ajouter", combiné avec seulement quelques enlevé par rapport à la taille de la liste. Il serait peut-être plus efficace de ne faire que quelques retraits plutôt qu’un grand nombre d’ajouts. Mais dans mon cas, la situation ne méritait pas une telle optimisation.


3
2017-11-14 16:57