Supprimer des éléments d'un HashSet tout en itérant [dupliquer]

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

  • itération à travers une Collection, en évitant la modification Concurrentemodificationexception lors de la suppression en boucle 24 réponses

donc, si j'essaie de supprimer des éléments D'un Java HashSet tout en itérant, je reçois un ConcurrentModificationException . Quelle est la meilleure façon de supprimer un sous-ensemble des éléments d'un HashSet comme dans l'exemple suivant?

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

Voici une solution, mais je ne pense pas qu'elle soit très élégante:

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!

103
demandé sur Adam Paynter 2009-07-10 19:52:36

7 réponses

vous pouvez itérer manuellement 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 motif 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 l'ont souligné, l'utilisation d'une boucle for est préférable parce qu'elle maintient la variable itératrice ( i dans ce cas) limitée à une portée plus petite.

160
répondu Adam Paynter 2009-07-10 18:46:08

la raison pour laquelle vous obtenez un ConcurrentModificationException est parce qu'une entrée est retirée via ensemble.supprimer () par opposition à .supprimer () . Si une entrée est supprimée via Set.supprimer () pendant qu'une itération est en cours, vous obtiendrez une exception de modification Concurrentemodificationexception. D'autre part, suppression des entrées via Iterator.supprimer () pendant que l'itération est supportée dans ce cas.

le nouveau pour boucle est agréable, mais malheureusement il ne fonctionne pas dans ce cas, parce que vous ne pouvez pas utiliser la référence Iterator.

si vous devez supprimer une entrée pendant l'itération, vous devez utiliser la forme longue qui utilise l'itérateur directement.

for (Iterator<Integer> it = set.iterator(); it.hasNext();) {
    Integer element = it.next();
    if (element % 2 == 0) {
        it.remove();
    }
}
17
répondu sjlee 2018-02-21 14:56:50

vous pouvez également remanier votre solution en supprimant 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);
9
répondu dfa 2009-07-10 16:01:07

Java 8 Collection a une belle méthode appelée removeIf qui rend les choses plus faciles et plus sûres. 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 -

8
répondu risoldi 2016-08-24 09:56:49

comme dit timber - "la collection Java 8 a une belle méthode 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.

6
répondu Getriax 2017-04-06 00:14:15

doit-il l'être tout en itérant? Si vous ne faites que filtrer ou sélectionner, je suggère D'utiliser Apache Commons CollectionUtils . Il y a des outils puissants et il rend votre code plus "cool."

Voici une implémentation qui devrait vous 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 type de prédicat fréquemment vous pouvez tirer que dans une variable statique pour réutiliser... dis quelque chose comme EVEN_NUMBER_PREDICATE . Certains peuvent voir ce code et le déclarer "difficile à lire", mais il semble plus propre lorsque vous tirez le prédicat dans un statique. Alors il est facile de voir que nous faisons un CollectionUtils.filter(...) et cela me semble plus lisible (pour moi) qu'un tas de boucles partout dans la création.

4
répondu dustmachine 2009-07-10 16:08:17

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);
}
2
répondu alex2k8 2010-02-21 13:43:06