Appeler remove en boucle foreach en 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 names = .... for (Ssortingng name : names) { // Do something names.remove(name). } 

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

 //Assume that the names list as duplicate ensortinges List names = .... for (Ssortingng name : names) { // Do something while (names.remove(name)); } 

Pour supprimer en toute sécurité une collection en l’itérant, vous devez utiliser un iterator.

Par exemple:

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

De la documentation Java :

Les iterators renvoyés par les méthodes iterator et listIterator de cette classe sont rapides: si la liste est modifiée structurellement à tout moment après la création de l’iterator, à l’exception des méthodes remove ou add de l’iterator, l’iterator lancera une exception ConcurrentModificationException. Ainsi, face à la modification simultanée, l’iterator échoue rapidement et proprement, au lieu de risquer un comportement arbitraire et non déterministe à un moment indéterminé dans le futur.

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

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

Le comportement d’un iterator 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.

Écrivez plutôt votre code:

 List names = .... Iterator it = names.iterator(); while (it.hasNext()) { Ssortingng name = it.next(); // Do something it.remove(); } 

Notez que le code appelle Iterator.remove , pas List.remove .

Addenda:

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 l’ Iterator . Cela pourrait modifier la collection d’une manière surprenante et affecter les opérations futures sur l’ Iterator .

La conception Java de la “boucle améliorée” consistait à ne pas exposer l’iterator à du code, mais la seule façon de supprimer un élément en toute sécurité consiste à accéder à l’iterator. Donc, dans ce cas, vous devez le faire à l’ancienne:

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

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

EDIT (addendum): Non, changer la liste de quelque manière que ce soit en dehors de la méthode iterator.remove () lors de l’itération causera des problèmes. La seule façon de contourner cela consiste à utiliser un CopyOnWriteArrayList, mais cela est vraiment destiné aux problèmes de concurrence.

La façon la moins coûteuse (en termes de lignes de code) de supprimer les doublons est de vider la liste dans un LinkedHashSet (puis de revenir dans une liste si vous en avez besoin). Cela préserve l’ordre d’insertion tout en supprimant les doublons.

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

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

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

 List 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 iterators.

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

 List names = .... // introduce a separate list to hold removing items List toRemove= new ArrayList(); for (Ssortingng name : names) { // Do something: perform conditional checks toRemove.add(name); } names.removeAll(toRemove); // now names list holds expected values 

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 simultanées telles que ConcurrentHashMap.

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 names = .... List reducedNames = .... for (Ssortingng name : names) { // Do something if (conditionToIncludeMet) reducedNames.add(name); } return reducedNames; 

La situation qui m’a amené à cette page impliquait un ancien code qui parcourait une liste en utilisant des indécies pour supprimer des éléments de la liste. Je voulais le remanier 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 qui n’avaient pas l’autorisation de la liste.

 List services = ... for (int i=0; i 

Pour inverser cela et ne pas utiliser le supprimer:

 List services = ... List 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 "ajout" coûteux, combiné avec seulement quelques supprimés 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.

  1. Essayez ceci et changez la condition en “HIVER” et vous vous demanderez:
 public static void main(Ssortingng[] args) { Season.add("Frühling"); Season.add("Sommer"); Season.add("Herbst"); Season.add("WINTER"); for (Ssortingng s : Season) { if(!s.equals("Sommer")) { System.out.println(s); continue; } Season.remove("Frühling"); } } 

Il est préférable d’utiliser un iterator 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é, cela peut entraîner une erreur.

Utilisation

.remove () of Interator ou

Utilisation

CopyOnWriteArrayList