125

因此,如果我在迭代时尝试从 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);

谢谢!

4

7 回答 7

193

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

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在这种情况下)限制在较小的范围内。

于 2009-07-10T15:55:20.883 回答
25

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

新的 for 循环很好,但不幸的是它在这种情况下不起作用,因为您不能使用 Iterator 引用。

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

for (Iterator<Integer> it = set.iterator(); it.hasNext();) {
    Integer element = it.next();
    if (element % 2 == 0) {
        it.remove();
    }
}
于 2009-07-12T04:54:31.023 回答
16

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-

于 2016-08-24T09:56:49.420 回答
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);
于 2009-07-10T16:01:07.380 回答
10

就像木头说的那样——“Java 8 Collection 有一个很好的方法,叫做 removeIf,它让事情变得更容易和更安全”

这是解决您的问题的代码:

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

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

于 2017-04-05T17:29:03.790 回答
5

这是更现代的流方法:

myIntegerSet.stream().filter((it) -> it % 2 != 0).collect(Collectors.toSet())

但是,这会产生一个新集合,因此如果它是一个非常大的集合,内存限制可能是一个问题。

编辑:此答案的先前版本建议使用 Apache CollectionUtils,但那是在蒸汽出现之前。

于 2009-07-10T16:08:17.910 回答
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);
}
于 2010-02-21T13:34:51.407 回答