136

So, if I try to remove elements from a Java HashSet while iterating, I get a ConcurrentModificationException. What is the best way to remove a subset of the elements from a HashSet as in the following example?

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

Here is a solution, but I don't think it's very elegant:

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

Thanks!

7 Answers7

201

You can manually iterate over the elements of the set:

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

You will often see this pattern using a for loop rather than a while loop:

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

As people have pointed out, using a for loop is preferred because it keeps the iterator variable (i in this case) confined to a smaller scope.

Adam Paynter
  • 46,244
  • 33
  • 149
  • 164
  • 5
    I prefer `for` to `while`, but each to his/her own. – Tom Hawtin - tackline Jul 10 '09 at 15:57
  • 1
    I also use `for` myself. I used `while` to hopefully make the example clearer. – Adam Paynter Jul 10 '09 at 15:57
  • 17
    I perfer `for` mostly because the iterator variable is then limited to the scope of the loop. – Kathy Van Stone Jul 10 '09 at 16:10
  • 1
    If `while` is used then the iterator's scope is larger than it needs to be. – Steve Kuo Jul 10 '09 at 17:42
  • 4
    I prefer the while because it looks cleaner to me. The scope of the iterator should not be an issue if you are factoring your code. See Becks book "Test Driven Development" or Fowler's "Refactoring" for more about factoring code. – nash Nov 04 '09 at 16:05
  • won't it throw ConcurrentModificationexception? – Sumit Kumar Saha Dec 11 '18 at 11:50
  • @SumitKumarSaha, no, it shouldn't if you are modifying the collection with the iterator, as is done in both examples here. If another party has modified the collection while you're iterating, or if you modify it outside the iterator, the behavior is unspecified: [javadoc](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Iterator.html#remove()) – chaserb Apr 23 '20 at 21:12
29

The reason you get a ConcurrentModificationException is because an entry is removed via Set.remove() as opposed to Iterator.remove(). If an entry is removed via Set.remove() while an iteration is being done, you will get a ConcurrentModificationException. On the other hand, removal of entries via Iterator.remove() while iteration is supported in this case.

The new for loop is nice, but unfortunately it does not work in this case, because you can't use the Iterator reference.

If you need to remove an entry while iteration, you need to use the long form that uses the Iterator directly.

for (Iterator<Integer> it = set.iterator(); it.hasNext();) {
    Integer element = it.next();
    if (element % 2 == 0) {
        it.remove();
    }
}
sjlee
  • 7,726
  • 2
  • 29
  • 37
19

Java 8 Collection has a nice method called removeIf that makes things easier and safer. From the 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.

Interesting note:

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

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

risoldi
  • 449
  • 5
  • 16
10

you can also refactor your solution removing the first loop:

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);
dfa
  • 114,442
  • 31
  • 189
  • 228
  • I would not recommend this as it introduces a hidden temporal coupling. – Romain F. Mar 04 '14 at 12:44
  • 1
    @RomainF. - What do you mean by hidden temporal coupling? Do you mean thread safe? Second, neither I would recommend this but the solution does have its pro. Super easy to read and hence maintainable. – saurabheights Jun 01 '17 at 09:15
  • Yes, the for-loop produces a side effect, but I agree that it may be the most readable solution, unless you are using Java 8. Otherwise, just use "removeIf" method. – Romain F. Jun 01 '17 at 12:43
  • I think this answer misses the point that the first loop was only there to have a `HashSet` from which to remove certain elements. – KeithWM Jul 11 '17 at 14:32
10

Like timber said - "Java 8 Collection has a nice method called removeIf that makes things easier and safer"

Here is the code that solve your problem:

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

Now your set contains only odd values.

xlm
  • 6,854
  • 14
  • 53
  • 55
Getriax
  • 521
  • 1
  • 8
  • 11
5

Here's the more modern streams approach:

myIntegerSet.stream().filter((it) -> it % 2 != 0).collect(Collectors.toSet())

However, this makes a new set, so memory constraints might be an issue if it's a really huge set.

EDIT: previous version of this answer suggested Apache CollectionUtils but that was before steams came about.

dustmachine
  • 10,622
  • 5
  • 26
  • 28
  • 1
    This answer really starts showing its age... There's a Java-8 way of doing this now which is arguably cleaner. – dustmachine Jun 16 '16 at 14:01
  • Is there a better method to use, or were you just referring to the ability to use a lambda in place of an anonymous inner class? – qwerty Jan 24 '21 at 22:02
  • 1
    Here's the more modern way: `myIntegerSet.stream().filter((it) -> it % 2 != 0).collect(Collectors.toSet())` – dustmachine Jan 27 '21 at 03:30
2

An other possible solution:

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

Or:

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

for(Integer element : copy) {
    if(element % 2 == 0)
        set.remove(element);
}
alex2k8
  • 42,496
  • 57
  • 170
  • 221
  • That (or creating an `ArrayList` out of the set) is the best solution if you happen to not only remove existing elements but also adding new ones to the set during the loop. – Giulio Piancastelli Mar 07 '16 at 17:40