I want to be able to remove multiple elements from a set while I am iterating over it. Initially I hoped that iterators were smart enough for the naive solution below to wor
You should call Iterator.remove
method.
Also note, that on most java.util
collections the remove
method will generate exception if the contents of the collection have changed. So, if the code is multi-threaded use extra caution, or use concurrent collections.
Normally when you remove an element from a collection while looping over the collection, you'll get a Concurrent Modification Exception. This is partially why the Iterator interface has a remove() method. Using an iterator is the only safe way to modify a collection of elements while traversing them.
The code would go something like this:
Set<SomeClass> set = new HashSet<SomeClass>();
fillSet(set);
Iterator<SomeClass> setIterator = set.iterator();
while (setIterator.hasNext()) {
SomeClass currentElement = setIterator.next();
if (setOfElementsToRemove(currentElement).size() > 0) {
setIterator.remove();
}
}
This way you'll safely remove all elements that generate a removal set from your setOfElementsToRemove().
EDIT
Based on a comment to another answer, this may be more what you want:
Set<SomeClass> set = new HashSet<SomeClass>();
Set<SomeClass> removalSet = new HashSet<SomeClass>();
fillSet(set);
for (SomeClass currentElement : set) {
removalSet.addAll(setOfElementsToRemove(currentElement);
}
set.removeAll(removalSet);
You could try the java.util.concurrent.CopyOnWriteArraySet
which gives you an iterator that is a snapshot of the set at the time of the iterator creation. Any changes you make to the set (i.e. by calling removeAll()
) won't be visible in the iterator, but are visible if you look at the set itself (and removeAll()
won't throw).
Copied from the Java API:
The List interface provides a special iterator, called a ListIterator, that allows element insertion and replacement, and bidirectional access in addition to the normal operations that the Iterator interface provides. A method is provided to obtain a list iterator that starts at a specified position in the list.
I thought I would point out that the ListIterator which is a special kind of Iterator is built for replacement.
It is possible to implement a Set
that allows its elements to be removed whilst iterating over it.
I think the standard implementations (HashSet, TreeSet etc.) disallow it because that means they can use more efficient algorithms, but it's not hard to do.
Here's an incomplete example using Google Collections:
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.google.common.base.Predicates;
import com.google.common.collect.ForwardingSet;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
public class ConcurrentlyModifiableSet<E>
extends ForwardingSet<E> {
/** Create a new, empty set */
public ConcurrentlyModifiableSet() {
Map<E, Boolean> map = new ConcurrentHashMap<E, Boolean>();
delegate = Sets.newSetFromMap(map);
}
@Override
public Iterator<E> iterator() {
return Iterators.filter(delegate.iterator(), Predicates.in(delegate));
}
@Override
protected Set<E> delegate() {
return this.delegate;
}
private Set<E> delegate;
}
Note: The iterator does not support the remove()
operation (but the example in the question does not require it.)
Any solution that involves removing from the set you're iterating while you're iterating it, but not via the iterator, will absolutely not work. Except possibly one: you could use a Collections.newSetFromMap(new ConcurrentHashMap<SomeClass, Boolean>(sizing params))
. The catch is that now your iterator is only weakly consistent, meaning that each time you remove an element that you haven't encountered yet, it's undefined whether that element will show up later in your iteration or not. If that's not a problem, this might work for you.
Another thing you can do is build up a toRemove
set as you go instead, then set.removeAll(itemsToRemove);
only at the end. Or, copy the set before you start, so you can iterate one copy while removing from the other.
EDIT: oops, I see Peter Nix had already suggested the toRemove
idea (although with an unnecessarily hand-rolled removeAll
).