迭代时从HashSet中删除元素

时间:2009-07-10 15:52:36

标签: java iteration hashmap hashset

因此,如果我在迭代时尝试从Java HashSet 中删除元素,我会得到一个 ConcurrentModificationException 。从 HashSet 中删除元素子集的最佳方法是什么,如下例所示?

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

这是一个解决方案,但我认为它不是很优雅:

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

谢谢!

7 个答案:

答案 0 :(得分:174)

您可以手动迭代集合的元素:

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

您经常会使用for循环而不是while循环来看到此模式:

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

正如人们所指出的那样,首选使用for循环是因为它将迭代器变量(在这种情况下为i)限制在较小的范围内。

答案 1 :(得分:19)

获得ConcurrentModificationException的原因是因为通过 Set.remove()删除了一个条目,而不是 Iterator.remove()。如果在迭代完成时通过 Set.remove()删除了一个条目,您将收到ConcurrentModificationException。另一方面,在这种情况下支持迭代时,通过 Iterator.remove()删除条目。

新的for循环很不错,但不幸的是它在这种情况下不起作用,因为你不能使用Iterator参考。

如果需要在迭代时删除条目,则需要使用直接使用Iterator的长格式。

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

答案 2 :(得分:10)

您还可以重构解决方案,删除第一个循环:

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

答案 3 :(得分:10)

Java 8 Collection有一个名为removeIf的好方法,可以使事情更简单,更安全。来自API文档:

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.

有趣的说明:

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

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

答案 4 :(得分:8)

就像木材说 - “Java 8 Collection有一个很好的方法叫做removeIf,让事情变得更容易,更安全”

以下是解决问题的代码:

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

现在你的集合只包含奇数值。

答案 5 :(得分:4)

迭代时是否需要?如果你正在做的只是过滤或选择我会建议使用Apache Commons CollectionUtils。那里有一些强大的工具,它使你的代码“更酷。”

这是一个应该提供您所需要的实现:

Set<Integer> myIntegerSet = new HashSet<Integer>();
// Integers loaded here
CollectionUtils.filter( myIntegerSet, new Predicate() {
                              public boolean evaluate(Object input) {
                                  return (((Integer) input) % 2 == 0);
                              }});

如果您发现自己经常使用相同类型的谓词,可以将其拉出到静态变量中以便重复使用...将其命名为EVEN_NUMBER_PREDICATE。有些人可能会看到该代码并声明它“难以阅读”,但当您将Predicate拉入静态时它看起来更清晰。然后很容易看出我们正在做一个CollectionUtils.filter(...),这看起来比我创造的一堆循环更具可读性。

答案 6 :(得分:2)

另一种可能的解决方案:

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

或者:

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

for(Integer element : copy) {
    if(element % 2 == 0)
        set.remove(element);
}