L'itération sur une Collection, en évitant ConcurrentModificationException lors de la suppression d'objets dans une boucle
Nous savons tous que vous ne pouvez pas faire ceci:
for (Object i : l) {
if (condition(i)) {
l.remove(i);
}
}
ConcurrentModificationException
etc... ce qui, apparemment, fonctionne parfois, mais pas toujours. Voici un code spécifique:
public static void main(String[] args) {
Collection<Integer> l = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
l.add(4);
l.add(5);
l.add(6);
}
for (int i : l) {
if (i == 5) {
l.remove(i);
}
}
System.out.println(l);
}
Cela, bien sûr, les résultats dans:
Exception in thread "main" java.util.ConcurrentModificationException
... même si plusieurs threads ne sont pas... de toute façon.
Quelle est la meilleure solution à ce problème? Comment puis-je supprimer un élément de la collection dans une boucle sans jeter cette exception?
Je suis aussi à l'aide d'un arbitraire Collection
ici, n'est pas nécessairement une ArrayList
, de sorte que vous ne pouvez pas compter sur get
.
- Note aux lecteurs: pour faire avoir une lecture de docs.oracle.com/javase/tutorial/collections/interfaces/..., il peut avoir un moyen plus facile d'obtenir ce que vous voulez faire.
Vous devez vous connecter pour publier un commentaire.
Itérateur.remove()
est sûr, vous pouvez l'utiliser comme ceci:Noter que
Itérateur.remove()
est la seule façon de modifier une collection au cours d'une itération; le comportement est indéfini si la collection sous-jacente est modifié de toute autre manière tandis que l'itération est en cours.Source: docs.oracle > L'Interface de Collecte
Et de même, si vous avez un
ListIterator
et souhaitez ajouter éléments, vous pouvez utiliserListIterator#ajouter
, pour la même raison, vous pouvez utiliserIterator#remove
— il est conçu pour permettre.Dans votre cas, vous avez essayé de supprimer à partir d'une liste, mais la même restriction s'applique si vous essayez de
put
dans unMap
lors de l'itération de son contenu.iterator.next()
appel dans la boucle for? Si non, quelqu'un peut m'expliquer pourquoi?iterator.next()
appel dans la boucle for, mais vous devrez aussi ajouter une autre vérification à l'aide deiterator.hasNext()
à assurez-vous que vous avez même un autre élément à récupérer.List.add
est "facultatif" dans ce même sens trop, mais vous ne pouvez pas dire que c'est "dangereux" pour l'ajouter à une liste.Cela fonctionne:
Je suppose que depuis une boucle foreach est sucre syntaxique pour réitérer, à l'aide d'un itérateur n'aide pas... mais il vous donne ce
.remove()
fonctionnalité.Avec Java 8, vous pouvez utiliser la nouvelle
removeIf
méthode. Appliqué à votre exemple:removeIf
utiliseIterator
etwhile
boucle. Vous pouvez le voir à java 8java.util.Collection.java
ArrayList
le remplacer pour des raisons de performances. Celui que vous mentionnez n'est que le défaut de mise en œuvre.equals
n'est pas utilisé à tous ici, donc il n'a pas à être mis en œuvre. (Mais bien sûr, si vous utilisezequals
dans votre test, puis elle doit être mise en œuvre de la façon dont vous le voulez.)Depuis que la question a déjà été répondu, c'est à dire le meilleur moyen est d'utiliser la méthode remove de l'itérateur de l'objet, je voudrais aller dans les détails de l'endroit où l'erreur
"java.util.ConcurrentModificationException"
est levée.Chaque classe de collection dispose d'une salle de classe qui implémente l'interface Iterator et fournit des méthodes comme
next()
,remove()
ethasNext()
.Le code suivant ressemble à quelque chose comme ça...
Ici la méthode de la
checkForComodification
est mis en œuvre commeDonc, comme vous pouvez le voir, si vous l'avez explicitement essayez de supprimer un élément de la collection. Il en résulte
modCount
différents deexpectedModCount
, résultant dans l'exceptionConcurrentModificationException
.Vous pouvez soit utiliser l'itérateur directement comme vous l'avez dit, ou bien de conserver une deuxième collecte et d'ajouter chaque élément que vous souhaitez supprimer de la nouvelle collection, puis removeAll à la fin. Cela vous permet de continuer à utiliser le type de sécurité de la boucle for-each, au prix de l'augmentation de l'utilisation de la mémoire et de temps processeur (ne devrait pas être un gros problème, sauf si vous avez vraiment, vraiment grand de listes ou d'un très ancien ordinateur)
Dans de tels cas, un truc commun est (était?) pour revenir en arrière:
Cela dit, je suis plus qu'heureux que vous avez de meilleures façons de Java 8, par exemple
removeIf
oufilter
sur les cours d'eau.ArrayList
s ou les collections similaires.for(int i = l.size(); i-->0;) {
?Même réponse que Claudius avec une boucle for:
Avec Eclipse Collections (anciennement GS Collections), la méthode
removeIf
définie sur MutableCollection fonctionne:Avec Java 8 Lambda syntaxe de ce qui peut être écrit comme suit:
L'appel à
Predicates.cast()
est nécessaire ici car un défautremoveIf
méthode a été ajouté sur lejava.util.Collection
interface en Java 8.Remarque: je suis un committer pour Eclipse Collections.
Faire une copie de la liste existante et effectuer une itération sur nouvelle copie.
Avec un traditionnel pour la boucle
i++
dans la boucle de la garde, plutôt que dans le corps de boucle.i++
l'incrémentation n'avait pas été conditionnelle - je vois maintenant que c'est pourquoi vous le faites dans le corps 🙂Les gens sont revendiquant ne peut pas supprimer à partir d'une Collecte de données par une boucle foreach. Je voulais juste faire remarquer que est techniquement incorrect et décrire exactement (je sais que l'OP question est tellement avancé que pour obvier à savoir ce) le code derrière cette hypothèse:
Ce n'est pas que vous ne pouvez pas retirer de l'itéré
Colletion
plutôt que vous ne pouvez pas continuer ensuite itération une fois que vous faites. D'où labreak
dans le code ci-dessus.Excuses si cette réponse est un peu le spécialiste de cas d'utilisation et plus adapté à l'original fil je suis arrivé ici, que l'un est marqué comme un doublon (en dépit de ce fil apparaissant plus nuancée), du présent et verrouillé.
Un
ListIterator
vous permet d'ajouter ou de supprimer des éléments dans la liste. Supposons que vous avez une liste deCar
objets:previous
méthode.J'ai une suggestion pour le problème ci-dessus. Pas besoin de liste secondaire ou le temps. Vous trouverez un exemple qui ferait la même chose mais de manière différente.
Cela permettrait d'éviter la Simultanéité d'Exception.
ArrayList
et ne peut donc pas compter surget()
. Sinon probablement une bonne approche, si.Collection
-Collection
interface ne comprend pasget
. (Bien que FWIWList
interface ne comprennent 'get').while
-lecture en boucle d'unList
. Mais +1 pour cette Réponse parce que c'est venu en premier.ConcurrentHashMap ou ConcurrentLinkedQueue ou ConcurrentSkipListMap peut être une autre option, car ils ne pourront jamais jeter tout ConcurrentModificationException, même si vous supprimez ou ajoutez un élément.
java.util.concurrent
paquet. Quelques autres/commune-de cas d'utilisation de classes de ce package sontCopyOnWriteArrayList
&CopyOnWriteArraySet
[mais non limité à ceux].ConcurrentModificationException
, de les utiliser dans un amélioré-pour-boucle peut encore causer des problèmes d'indexation (j'.e: est-ce encore de sauter des éléments, ouIndexOutOfBoundsException
...)Une autre façon est de créer une copie de votre liste de tableaux:
}
i
n'est pas unindex
mais à la place de l'objet. Peut-être l'appelerobj
serait plus appropriée.En cas ArrayList:remove(int index)- si(index est le dernier élément de la position) il évite sans
System.arraycopy()
et ne prend pas le temps pour cela.arraycopy temps augmente si(index diminue), par la façon dont les éléments de la liste diminue aussi!
le meilleur efficaces supprimer façon est - retrait de ses éléments dans l'ordre décroissant:
while(list.size()>0)list.remove(list.size()-1);
//prend O(1)while(list.size()>0)list.remove(0);
//prend O(factorielle(n))Le hic, c'est le après le retrait de l'élément de la liste si vous ignorez l'itérateur interne.next() de l'appel. il fonctionne encore! Bien que je n'ai pas de proposer d'écrire du code comme cela, il aide à comprendre le concept derrière elle 🙂
Cheers!
Exemple de thread-safe modification de collection:
Je sais que cette question est trop vieux pour être sur Java 8, mais pour ceux à l'aide de Java 8, vous pouvez facilement utiliser removeIf():
Je sais que cette question suppose simplement une
Collection
, et pas plus précisément de touteList
. Mais pour ceux qui lisent cette question, qui sont en effet de travailler avec unList
de référence, vous pouvez éviterConcurrentModificationException
avec unwhile
-boucle (lors de la modification de l'intérieur) à la place si vous voulez éviter deIterator
(si vous voulez l'éviter en général, ou de l'éviter spécifiquement pour réaliser une boucle de commande différent du début à la fin en s'arrêtant à chaque élément [je crois que c'est le seul ordreIterator
lui-même peut faire]):*Mise à jour: Voir les commentaires ci-dessous qui permettent de préciser l'analogie est aussi possible avec l' traditionnel-pour-boucle.
Pas ConcurrentModificationException de ce code.
Il nous voyons en boucle démarre pas au début, et ne pas s'arrêter à chaque élément (qui, je crois
Iterator
lui-même ne peut pas le faire).FWIW, nous voyons aussi
get
d'être appelélist
, qui ne pouvait pas être fait si sa référence était justeCollection
(à la place de celui, plus spécifique,List
- type deCollection
) -List
interface comprendget
, maisCollection
interface ne fonctionne pas. Si ce n'est pour la différence, alors leslist
de référence pourrait être plutôt uneCollection
[et donc, techniquement, cette Réponse serait alors une Réponse directe, au lieu d'une Réponse tangentielle].FWIWW même code fonctionne toujours, après modification de commencer au début à s'arrêter à chaque élément (comme
Iterator
l'ordre):ConcurrentModificationException
, mais pas le traditionnel-pour-boucle (l'autre Réponse utilise) - ne réalisant pas qu'auparavant, c'est pourquoi j'ai été motivé à écrire cette Réponse (j'ai pensé, à tort, alors qu'il était tous pour des boucles qui permettrait de lancer l'Exception).cela pourrait ne pas être le meilleur moyen, mais pour la plupart des cas, cela devrait acceptable:
je ne peux pas me rappeler où j'ai lu cela à partir de... pour justiness je vais faire de ce wiki dans l'espoir que quelqu'un le trouve ou tout simplement ne gagnent pas rep je ne le méritent pas.
Les expressions Lambda et méthodes de Collecte dans le Jdk 8 est très Pratique et ajoute du sucre syntaxique .
La méthode
removeIf
boucles à travers la collecte et de filtres avec Prédicat. Un Prédicat est fonction d'un argument qui retournent un booléen...Tout comme
boolean _bool = (str) -> str.equals("text");