为啥它不会从集合中删除?

Posted

技术标签:

【中文标题】为啥它不会从集合中删除?【英文标题】:Why won't it remove from the set?为什么它不会从集合中删除? 【发布时间】:2010-11-08 08:47:22 【问题描述】:

这个错误我花了一段时间才找到...

考虑这种方法:

public void foo(Set<Object> set)

    Object obj=set.iterator().next();
    set.remove(obj)

我使用非空哈希集调用该方法,但不会删除任何元素!

为什么会这样?

【问题讨论】:

在 Java 中是否允许在迭代集合时弄乱集合?这是 C# 中的一大禁忌。 实际上,在 C# 中,在集合更改后重新使用迭代器是错误的。从使用迭代器获得的集合中删除一个元素是可以的。 并非如此。你也可以在 C# 中做到这一点。除非您这样做,否则对迭代器的任何进一步操作都将导致异常。不过在这种情况下不是问题。 @DrJokepu - 如果您正在迭代集合,那将是一个禁忌,您应该在迭代器上调用 remove,但看起来@Yvon Rozijin 只是使用迭代器来获取第一个列表中的元素。 java.util.Iterator 上没有 dispose,所以我们在迭代器运行时调用 remove 【参考方案1】:

我的类似案例:

这对我不起作用。我的班级组需要@Override equalshashCode,如下所示:

@Override
public int hashCode() 
    final int prime = 31;
    int result = 1;
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    return result;


@Override
public boolean equals(Object obj) 
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    Group other = (Group) obj;
    if (id == null) 
        if (other.id != null) return false;
     else if (!id.equals(other.id)) return false;
    return true;

这会强制通过 id 字段比较 POJO,而不考虑月相。

【讨论】:

【参考方案2】:

对于 HashSet,如果对象的 hashCode 在添加到集合后发生更改,则会发生这种情况。然后 HashSet.remove() 方法可能会在错误的 Hash 桶中查找并找不到它。

如果你执行了 iterator.remove(),这可能不会发生,但无论如何,将对象存储在 hashCode 可以更改的 HashSet 中是等待发生的意外(正如你已经发现)。

【讨论】:

这听起来很正确 - 那些使用类的所有字段的自动生成 hashCode 方法,其中一些是可变的,可能会导致这种情况。 刚刚尝试将 remove 与在添加后其哈希码已更改的对象一起使用,并且删除确实失败了。顺便提一下,iterator.remove() 也是如此。 这确实是个问题。通过使用 List 来解决(无论如何,集合中的元素永远不会超过几个,所以这里没有大的性能损失)。 你刚刚把我从精神错乱中救了出来。谢谢!【参考方案3】:

拼图?如果 Object.hashCodeObject.equals 或“哈希集”未正确实现(例如,请参阅 java.net.URL - 使用 URI)。

此外,如果集合(直接或间接)包含自身,则可能会发生一些奇怪的事情(具体的实现和月相依赖)。

【讨论】:

嗨,Tom - 迭代器返回的对象与传递给 Set.remove(Object) 的实例相同,因此等于必须非常混乱才能导致删除失败。同样,包含对自身的引用的集合会有点混乱,但这里没有递归,所以它应该仍然可以工作(尽管由于堆栈溢出创建迭代器,它没有 HashSet - 看起来有一些递归在实施中)。我认为您在 Set - Nick 的实施方面走在了正确的轨道上 只需阅读@Adrian Pronk 的关于在添加对象后更改哈希码的答案 - 猜想这仍然算作混乱...... 有很多方法可以实现不符合规范的方法!您实际上可以有一个有效的实现,但从另一个线程更改状态。 (顺便说一句:谁对此投了反对票:你想解释一下吗?) 嗨,汤姆——那是我。当时,您的答案比 Adrian 的答案(尚未被接受)获得更多的赞成票,我已经测试并发现它是正确的。我还觉得对 hashCode 和 equals 的引用是模糊的,正如我在之前的评论中指出的那样,后面的(equals)我觉得错过了将同一个对象传递给 remove 方法,虽然我同意多个线程可能会导致这种情况,在问题或答案中没有提及线程 - 因此我认为它有必要投反对票,显然我相信你不会理解任何个人问题 - 干杯尼克【参考方案4】:

我不禁觉得(部分)问题在于集合是按值传递的,而不是按引用传递的。不过我在 Java 方面没有太多经验,所以我可能完全错了。

【讨论】:

是的,Java 中的对象永远不能按值传递。 (原语是。) @Sean Owen - 一切都是按值传递的,在对象的情况下,它是传递的引用(指向对象的指针)的值。 尼克 - 传递一个作为引用/指针的值是通过引用传递,否则通过引用传递意味着什么?【参考方案5】:

set的实现类型是什么,set里面有哪些对象?

如果是 HashSet,请确保对象的 hashCode() 方法的值在 set.put(...)set.remove(...) 之间保持不变。 如果它是 TreeSet,请确保未对对象进行任何修改,这些修改会影响集合的比较器或对象的 compareTo 方法。

在这两种情况下,set.put(...)set.remove(...) 之间的代码都违反了各自类实现定义的约定。根据经验,使用不可变对象作为集合内容(和 Map 键)是一个好主意。就其本质而言,此类对象在存储在集合中时无法更改。

如果您正在使用其他集合实现,请查看其 JavaDoc 以了解其合同;但通常equalshashCode 在对象包含在集合中时必须保持不变。

【讨论】:

【参考方案6】:

应该是:

public void foo(Set<Object> set)

    Iterator i = set.iterator();
    i.next();
    i.remove();

?

这个错误可能与以下有关:

public void remove()

迭代器的行为是 未指定,如果基础 集合被修改,而 迭代正在以任何方式进行 除了调用这个方法。

(Reference)

【讨论】:

这将是一种更好的方法(虽然不知道为什么要从集合中删除随机元素)。 HashSet.remove 在原始代码中是可以的,因为不再使用迭代器。【参考方案7】:

除了缺少的';'在set.remove(obj) 之后,可能在三种情况下发生(引用自javadoc)。

ClassCastException - if the type of the specified element is incompatible with this set (optional).
NullPointerException - if the specified element is null and this set does not support null elements (optional). 
UnsupportedOperationException - if the remove method is not supported by this set.

你也可以试试:

public void foo(Set<Object> set)

    Object obj=set.iterator().next();
    iterator.remove();

【讨论】:

这是你必须要做的。除了调用 iterator.remove() 方法之外,如果您在迭代时删除某些内容,集合类不保证会发生什么(这可能会为某些集合类抛出“UnsupportedOperatrion”。

以上是关于为啥它不会从集合中删除?的主要内容,如果未能解决你的问题,请参考以下文章

为啥删除的 Maven 模块不会从 SonarQube 中消失?

使用协议从集合视图单元格中删除核心数据

为啥我不能删除这个表格?

当我所做的只是一个列表时,为啥 Hibernate 会删除我的集合条目?

为啥 Node 中的 PassportJS 不会在注销时删除会话

SwipeCellKit:为啥从列表中删除一个项目,不更新 UITableview?