在迭代时从HashSet中移除元素

所以,如果我在迭代的时候尝试从Java HashSet中移除元素,我得到一个ConcurrentModificationExceptionexception 。 如下例所示,从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); 

这是一个解决scheme,但我不认为它是非常优雅的:

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

谢谢!

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

 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循环是首选的,因为它将迭代器variables(在这种情况下是i )限制在一个较小的范围内。

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

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

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

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

您也可以重构您的解决scheme删除第一个循环:

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

迭代时是否需要? 如果你只是在过滤或select,我会build议使用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); }}); 

如果你发现自己经常使用同一种谓词,你可以把它放到一个静态variables中重用…把它命名为EVEN_NUMBER_PREDICATE 。 有些人可能会看到这个代码,并声明“难以阅读”,但是当你把谓词拉出来的时候,看起来更干净。 然后,很容易看到我们正在做一个CollectionUtils.filter(...)而且这对我来说更可读,而不是整个创build过程中的一堆循环。

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-

其他可能的解决scheme:

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

像木材说的 – “Java 8 Collection有一个很好的方法叫removeIf让事情变得更简单和更安全”

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

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

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