在迭代时从散列集中删除元素

因此,如果我尝试在迭代时从 JavaHashSet中删除元素,就会得到一个 异常。如下例所示,从 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);

谢谢!

145413 次浏览

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

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)限制在较小的范围内。

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

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

以下是更现代的流方法:

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

但是,这会产生一个新的集合,因此如果集合非常庞大,内存约束可能会成为一个问题。

编辑: 这个答案的前一个版本建议使用 ApacheCollectionUtils,但那是在蒸汽出现之前。

获得 ConcurrentModificationException的原因是,一个条目是通过 设置、删除()而不是 迭代器删除的。如果在进行迭代时通过 设置、删除()删除某个条目,则会得到一个 ConcurrentModficationException。另一方面,本例支持通过 迭代器删除条目,同时支持迭代。

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

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

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

另一种可能的解决办法是:

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

Java8集合有一个很好的方法叫做 RemoveIf,它使事情变得更容易和更安全:

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

就像 Wood 说的“ Java8 Collection 有一个很好的方法叫 RemoveIf 这让事情变得更简单更安全”

下面是解决问题的代码:

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

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