关于java:迭代时从HashSet中删除元素

Remove Elements from a HashSet while Iterating

本问题已经有最佳答案,请猛点这里访问。

因此,如果我试图在迭代过程中从Java哈希集中删除元素,则会得到一个CONTRONTRONMODIGATION异常。如下面的示例所示,从哈希集中删除元素子集的最佳方法是什么?

1
2
3
4
5
6
7
8
9
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);

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

1
2
3
4
5
6
7
8
9
10
11
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);

谢谢!


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

1
2
3
4
5
6
7
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {
    Integer element = iterator.next();
    if (element % 2 == 0) {
        iterator.remove();
    }
}

您经常会看到使用for循环而不是while循环的这种模式:

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

正如人们所指出的,使用for循环是首选的,因为它将迭代器变量(本例中的i限制在较小的范围内。


获得ConcurrentModificationException的原因是通过set.remove()删除条目,而不是通过iterator.remove()。如果在迭代过程中通过set.remove()删除一个条目,您将得到一个ConcurrentModificationException。另一方面,在本例中支持迭代的同时,通过迭代器.remove()删除条目。

新的for循环很好,但不幸的是,它在本例中不起作用,因为您不能使用迭代器引用。

如果您需要在迭代时删除一个条目,那么您需要使用直接使用迭代器的长格式。

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


Java 8集合有一个很好的方法,叫做ReaveIf,它使事情变得更容易和更安全。从API文档:

1
2
3
4
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.

有趣的提示:

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

来自:http://DOCS.COM/JavaSe/ 8 /DOCS/API/Java/UTL/Copy.html


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

1
2
3
4
5
6
7
8
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);


就像木材说的那样:"Java 8的集合有一个很好的方法,叫做ReaveVE,它使事情变得更容易和更安全"。

下面是解决您问题的代码:

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

现在您的集合只包含奇数。


它是否需要在迭代时进行?如果您所做的只是过滤或选择,我建议您使用ApacheCommonsCollectionUtils。这里有一些强大的工具,它使您的代码"更酷"。

下面是一个实现,它应该提供您所需要的:

1
2
3
4
5
6
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。有些人可能会看到该代码并将其声明为"难以读取",但当您将谓词拉入静态时,它看起来更清晰。然后很容易看出我们正在做一个CollectionUtils.filter(...),对我来说,这看起来比整个创作过程中的一系列循环更易读。


另一种可能的解决方案:

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

或:

1
2
3
4
5
6
7
Integer[] copy = new Integer[set.size()];
set.toArray(copy);

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