Removing items from a collection in java while iterating over it

后端 未结 10 1737

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

相关标签:
10条回答
  • 2020-11-28 09:05

    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.

    0 讨论(0)
  • 2020-11-28 09:12

    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);
    
    0 讨论(0)
  • 2020-11-28 09:12

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

    0 讨论(0)
  • 2020-11-28 09:13

    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.

    0 讨论(0)
  • 2020-11-28 09:14

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

    0 讨论(0)
  • 2020-11-28 09:15

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

    0 讨论(0)
提交回复
热议问题