迭代时从HashSet中删除元素

115 java iteration hashmap hashset

因此,如果我在迭代时尝试从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);
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)

谢谢!

Ada*_*ter 179

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

Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {
    Integer element = iterator.next();
    if (element % 2 == 0) {
        iterator.remove();
    }
}
Run Code Online (Sandbox Code Playgroud)

您经常会使用for循环而不是while循环来看到此模式:

for (Iterator<Integer> i = set.iterator(); i.hasNext();) {
    Integer element = i.next();
    if (element % 2 == 0) {
        i.remove();
    }
}
Run Code Online (Sandbox Code Playgroud)

正如人们所指出的那样,使用for循环是首选,因为它将迭代器变量(i在这种情况下)保持在较小的范围内.

  • 我主要是因为迭代器变量被限制在循环的范围内. (15认同)
  • 我更喜欢"for"和"while",但每个人都喜欢他/她自己. (5认同)
  • 我更喜欢一段时间,因为它看起来更干净.如果要分解代码,迭代器的范围应该不是问题.有关分解代码的更多信息,请参阅Becks书籍"测试驱动开发"或Fowler的"重构". (4认同)

sjl*_*lee 19

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

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

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

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


小智 11

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.
Run Code Online (Sandbox Code Playgroud)

有趣的说明:

The default implementation traverses all elements of the collection using its iterator(). 
Each matching element is removed using Iterator.remove().
Run Code Online (Sandbox Code Playgroud)

来自:https: //docs.oracle.com/javase/8/docs/api/java/util/Collection.html#removeIf-java.util.function.Predicate-

  • 一个例子:`integerSet.removeIf(integer-&gt; integer.equals(5));` (3认同)

dfa*_*dfa 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);
Run Code Online (Sandbox Code Playgroud)


Get*_*iax 8

就像木材说的那样 - "Java 8 Collection有一个很好的方法叫做removeIf,让事情变得更容易,更安全"

以下是解决问题的代码:

set.removeIf((Integer element) -> {
    return (element % 2 == 0);
});
Run Code Online (Sandbox Code Playgroud)

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


dus*_*ine 5

这是更现代的流方法:

myIntegerSet.stream().filter((it) -> it % 2 != 0).collect(Collectors.toSet())
Run Code Online (Sandbox Code Playgroud)

然而,这会产生一个新的集合,所以如果它是一个非常大的集合,内存限制可能是一个问题。

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