【问题标题】:Remove Elements from a HashSet while Iterating [duplicate]迭代时从 HashSet 中删除元素 [重复]
【发布时间】: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);

谢谢!

【问题讨论】:

    标签: java iteration hashmap hashset


    【解决方案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) -&gt; 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-&gt; integer.equals(5));
    【解决方案7】:

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

    这是解决您问题的代码:

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

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

    【讨论】:

      猜你喜欢
      • 2015-06-30
      • 1970-01-01
      • 2020-04-22
      • 2013-01-23
      • 1970-01-01
      • 2020-05-21
      • 2020-06-09
      • 2012-05-13
      • 2011-02-21
      相关资源
      最近更新 更多