2009-07-10 10 views
98

Donc, si je tente de supprimer des éléments d'un Java HashSet tout itérer, je reçois un ConcurrentModificationException. Quel est le meilleur moyen de supprimer un sous-ensemble d'éléments d'un HashSet comme dans l'exemple suivant?supprimer des éléments d'un HashSet tout Itération

Set<Integer> set = new HashSet<Integer>(); 

for(int i = 0; i < 10; i++) 
    set.add(i); 

// Throws ConcurrentModificationException 
for(Integer element : set) 
    if(element % 2 == 0) 
     set.remove(element); 

est ici une solution, mais je ne pense pas qu'il est très élégant:

Set<Integer> set = new HashSet<Integer>(); 
Collection<Integer> removeCandidates = new LinkedList<Integer>(); 

for(int i = 0; i < 10; i++) 
    set.add(i); 

for(Integer element : set) 
    if(element % 2 == 0) 
     removeCandidates.add(element); 

set.removeAll(removeCandidates); 

Merci!

Répondre

154

Vous pouvez manuellement itérer sur les éléments de l'ensemble:

Iterator<Integer> iterator = set.iterator(); 
while (iterator.hasNext()) { 
    Integer element = iterator.next(); 
    if (element % 2 == 0) { 
     iterator.remove(); 
    } 
} 

Vous verrez souvent ce modèle en utilisant une boucle for plutôt qu'une boucle while:

for (Iterator<Integer> i = set.iterator(); i.hasNext();) { 
    Integer element = i.next(); 
    if (element % 2 == 0) { 
     i.remove(); 
    } 
} 

Comme les gens ont souligné , en utilisant une boucle for est préférable car il conserve la variable itérateur (i dans ce cas) confinée à une portée plus petite.

+5

Je préfère 'for' à' while', mais chacun à lui-même. –

+1

J'utilise aussi 'for' moi-même. J'ai utilisé 'while' pour espérer rendre l'exemple plus clair. –

+14

Je préfère principalement 'for' car la variable itérateur est alors limitée à la portée de la boucle. –

9

vous pouvez également Refactoriser votre solution retrait de la première boucle:

Set<Integer> set = new HashSet<Integer>(); 
Collection<Integer> removeCandidates = new LinkedList<Integer>(set); 

for(Integer element : set) 
    if(element % 2 == 0) 
     removeCandidates.add(element); 

set.removeAll(removeCandidates); 
+0

super truc, merci – Buffalo

+0

Je ne recommanderais pas cela car il introduit un couplage temporel caché. –

+1

@RomainF. - Que voulez-vous dire par couplage temporel caché? Voulez-vous dire thread sécurisé? Deuxièmement, je ne recommanderais pas cela, mais la solution a ses avantages. Super facile à lire et donc maintenable. – saurabheights

4

-t-il besoin d'être tout itérer? Si tout ce que vous faites est le filtrage ou la sélection, je suggère d'utiliser Apache Commons CollectionUtils. Il y a quelques outils puissants là-bas et cela rend votre code "plus cool".

est ici une implémentation qui devrait fournir ce dont vous avez besoin:

Set<Integer> myIntegerSet = new HashSet<Integer>(); 
// Integers loaded here 
CollectionUtils.filter(myIntegerSet, new Predicate() { 
           public boolean evaluate(Object input) { 
            return (((Integer) input) % 2 == 0); 
           }}); 

Si vous vous trouvez en utilisant le même genre de prédicat vous pouvez souvent tirer que sur dans une variable statique pour la réutilisation ... nommez quelque chose comme . Certains peuvent voir ce code et le déclarer "difficile à lire", mais il semble plus clair quand vous sortez le Predicate dans un statique. Ensuite, il est facile de voir que nous faisons un CollectionUtils.filter(...) et cela me semble plus lisible qu'un tas de boucles partout dans la création.

+0

Cette réponse commence vraiment à montrer son âge ... Il y a une façon Java-8 de le faire maintenant qui est sans doute plus propre. – dustmachine

16

La raison pour laquelle vous obtenez un ConcurrentModificationException est parce qu'une entrée est supprimée par Set.remove() par opposition à Iterator.remove(). Si une entrée est supprimée via Set.remove() alors qu'une itération est en cours, vous obtiendrez une exception ConcurrentModificationException. D'autre part, la suppression des entrées via Iterator.remove() tandis que l'itération est prise en charge dans ce cas.

La nouvelle boucle for est sympa, mais malheureusement elle ne fonctionne pas dans ce cas, car vous ne pouvez pas utiliser la référence Iterator.

Si vous devez supprimer une entrée pendant l'itération, vous devez utiliser le formulaire long qui utilise directement l'Iterator.

for (Iterator<Integer> it = set.iterator(); it.hasNext();) { 
    Integer element = it.next(); 
    if (element % 2 == 0) { 
     it.remove(); 
    } 
} 
+0

@ Votre code ne devrait-il pas vraiment l'appeler it.next()? – saurabheights

+1

Merci pour cela. Fixé. – sjlee

+0

À quel moment l'élément est-il instancié? –

2

Une autre solution possible:

for(Object it : set.toArray()) { /* Create a copy */ 
    Integer element = (Integer)it; 
    if(element % 2 == 0) 
     set.remove(element); 
} 

Ou:

Integer[] copy = new Integer[set.size()]; 
set.toArray(copy); 

for(Integer element : copy) { 
    if(element % 2 == 0) 
     set.remove(element); 
} 
+0

Cela (ou créer un ArrayList hors de l'ensemble) est la meilleure solution si vous supprimez non seulement des éléments existants mais aussi en ajoutant de nouveaux à l'ensemble pendant la boucle. –

6

Java 8 Collection a une méthode bien appelé removeif qui rend les choses plus facile et plus sûr. De l'API docs:

default boolean removeIf(Predicate<? super E> filter) 
Removes all of the elements of this collection that satisfy the given predicate. 
Errors or runtime exceptions thrown during iteration or by the predicate 
are relayed to the caller. 

Note intéressante:

The default implementation traverses all elements of the collection using its iterator(). 
Each matching element is removed using Iterator.remove(). 

De: https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html#removeIf-java.util.function.Predicate-

+0

Un exemple: 'integerSet.removeIf (integer-> integer.equals (5));' – Jelle

4

comme le bois dit - « Java 8 Collection a une méthode bien appelé removeif qui rend les choses plus facile et plus sûr "

Voici le code qui résout votre problème:

set.removeIf((Integer element) -> { 
    return (element % 2 == 0); 
}); 

Votre ensemble contient maintenant seulement des valeurs impaires.