迭代时从 HashSet 中删除元素 [重复]
Posted
技术标签:
【中文标题】迭代时从 HashSet 中删除元素 [重复]【英文标题】:Remove Elements from a HashSet while Iterating [duplicate] 【发布时间】:2010-11-09 18:31:08 【问题描述】:所以,如果我在迭代时尝试从 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);
谢谢!
【问题讨论】:
【参考方案1】:您可以手动迭代集合的元素:
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
)限制在较小的范围内。
【讨论】:
我更喜欢for
而不是while
,但每个人都有他/她自己的。
我自己也使用for
。我使用while
希望使示例更清晰。
我更喜欢for
,主要是因为迭代器变量被限制在循环的范围内。
如果使用while
,则迭代器的作用域比它需要的大。
我更喜欢 while,因为它对我来说看起来更干净。如果您正在分解代码,迭代器的范围应该不是问题。有关分解代码的更多信息,请参阅 Becks 的书“测试驱动开发”或 Fowler 的“重构”。【参考方案2】:
您还可以重构您的解决方案,删除第一个循环:
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);
【讨论】:
我不推荐这样做,因为它引入了隐藏的时间耦合。 @RomainF。 - 隐藏的时间耦合是什么意思?你的意思是线程安全的吗?其次,我也不建议这样做,但该解决方案确实有其优点。超级容易阅读,因此易于维护。 是的,for循环会产生副作用,但我同意它可能是最易读的解决方案,除非您使用的是Java 8。否则,只需使用“removeIf”方法。 我认为这个答案忽略了第一个循环只有一个HashSet
可以从中删除某些元素。【参考方案3】:
这是更现代的流方法:
myIntegerSet.stream().filter((it) -> it % 2 != 0).collect(Collectors.toSet())
但是,这会产生一个新集合,因此如果它是一个非常大的集合,内存限制可能会成为一个问题。
编辑:此答案的先前版本建议使用 Apache CollectionUtils,但那是在蒸汽出现之前。
【讨论】:
这个答案真的开始显示它的年龄......现在有一种 Java-8 方法可以做到这一点,可以说是更干净。 有没有更好的方法可以使用,或者您只是指使用 lambda 代替匿名内部类的能力? 这是更现代的方式:myIntegerSet.stream().filter((it) -> it % 2 != 0).collect(Collectors.toSet())
【参考方案4】:
您获得ConcurrentModificationException
的原因是通过Set.remove() 而非Iterator.remove() 删除条目。如果在完成迭代时通过 Set.remove() 删除条目,您将收到 ConcurrentModificationException。另一方面,在这种情况下支持迭代时通过 Iterator.remove() 删除条目。
新的 for 循环很不错,但不幸的是它在这种情况下不起作用,因为您不能使用迭代器引用。
如果您需要在迭代时删除条目,则需要使用直接使用迭代器的长格式。
for (Iterator<Integer> it = set.iterator(); it.hasNext();)
Integer element = it.next();
if (element % 2 == 0)
it.remove();
【讨论】:
@你的代码不应该调用 it.next() 吗? 谢谢。固定。 “元素”在什么时候被实例化? 呃。固定的。谢谢。【参考方案5】:另一种可能的解决方案:
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);
【讨论】:
如果您碰巧在循环期间不仅删除了现有元素,而且还向集合中添加了新元素,那么(或从集合中创建一个ArrayList
)是最好的解决方案。【参考方案6】:
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-
【讨论】:
一个例子:integerSet.removeIf(integer-> integer.equals(5));
【参考方案7】:
就像木头说的那样——“Java 8 Collection 有一个很好的方法叫做 removeIf,它可以让事情变得更简单、更安全”
这是解决您问题的代码:
set.removeIf((Integer element) ->
return (element % 2 == 0);
);
现在你的集合只包含奇数。
【讨论】:
以上是关于迭代时从 HashSet 中删除元素 [重复]的主要内容,如果未能解决你的问题,请参考以下文章