Supprimer des éléments d’un HashSet lors de l’itération

Donc, si j’essaie de supprimer des éléments d’un Java HashSet lors d’une itération, j’obtiens une exception ConcurrentModificationException . Quelle est la meilleure façon de supprimer un sous-ensemble des éléments d’un HashSet, comme dans l’exemple suivant?

Set set = new HashSet(); for(int i = 0; i < 10; i++) set.add(i); // Throws ConcurrentModificationException for(Integer element : set) if(element % 2 == 0) set.remove(element); 

Voici une solution, mais je ne pense pas que ce soit très élégant:

 Set set = new HashSet(); Collection removeCandidates = new LinkedList(); 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!

Vous pouvez effectuer une itération manuelle sur les éléments de l’ensemble:

 Iterator 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 i = set.iterator(); i.hasNext();) { Integer element = i.next(); if (element % 2 == 0) { i.remove(); } } 

Comme les gens l’ont fait remarquer, utiliser une boucle for est préférable car elle garde la variable iterator (dans ce cas) limitée à une scope plus petite.

La raison pour laquelle vous obtenez une exception ConcurrentModificationException est due au fait qu’une entrée est supprimée via 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 () pendant que l’itération est prise en charge dans ce cas.

Le nouveau pour la boucle est bien, mais malheureusement, cela ne fonctionne pas dans ce cas, car vous ne pouvez pas utiliser la référence Iterator.

Si vous devez supprimer une entrée lors de l’itération, vous devez utiliser le formulaire long qui utilise directement l’iterator.

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

vous pouvez également refactoriser votre solution en supprimant la première boucle:

 Set set = new HashSet(); Collection removeCandidates = new LinkedList(set); for(Integer element : set) if(element % 2 == 0) removeCandidates.add(element); set.removeAll(removeCandidates); 

Java 8 Collection possède une méthode intéressante appelée removeIf qui rend les choses plus faciles et plus sûres. A partir des documents API:

 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-

Comme le bois a dit – “Java 8 Collection a une méthode intéressante appelée removeIf qui rend les choses plus faciles et plus sûres”

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

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

Maintenant, votre ensemble ne contient que des valeurs impaires.

A-t-il besoin d’être en itération? Si tout ce que vous faites consiste à filtrer ou à sélectionner, je suggérerais d’utiliser Apache Commons CollectionUtils . Il existe des outils puissants qui rendent votre code plus “cool”.

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

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

Si vous utilisez fréquemment le même type de prédicat, vous pouvez le réutiliser dans une variable statique pour le réutiliser … nommez-le quelque chose comme EVEN_NUMBER_PREDICATE . Certains peuvent voir ce code et le déclarer “difficile à lire” mais il semble plus propre lorsque vous extrayez le prédicat dans un fichier statique. Ensuite, il est facile de voir que nous faisons un CollectionUtils.filter(...) et cela semble plus lisible (pour moi) qu’un tas de boucles dans toute la création.

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