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!
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();
}
}
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().
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);
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.
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.
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);
}
Source: Stackoverflow.com