繁体   English   中英

HashSet.contains 不应该返回 false

[英]HashSet.contains returns false when it shouldn't

我有这个代码:

public class Tray {

private Set<Block> blocks;

private int numColumns;
private int numRows;

//constructor
public Tray (int numRows, int numColumns){
    this.numColumns = numColumns;
    this.numRows = numRows;
    blocks = new HashSet<>();
}

public boolean satisfiesGoal(Tray other){

    Block thisBlock = this.blocks.iterator().next();
    Block otherBlock = other.blocks.iterator().next();

    boolean hashesEqual = thisBlock.hashCode() == otherBlock.hashCode(); // this is true

    boolean areEqual = thisBlock.equals(otherBlock) && otherBlock.equals(thisBlock); // this is true

    boolean contains = this.blocks.contains(otherBlock); // this is false, why?
    return contains;
}

在主要方法中,我已将 2 个块添加到各自的托盘中。 根据调试器,变量“hashesEqual”和“areEqual”为真,但布尔值“contains”为假。 关于为什么根据“equals”方法,2 个对象的哈希值会相等,但不会在 HashSet 中包含相等对象的任何想法?

如果在将对象添加到 HashSet以影响它们的相等性和哈希码的方式修改对象,则会发生此问题。 由于未在哈希表的正确槽中找到与其新值相对应的对象,该集合将出现故障。

同样,如果您修改用作键的对象,HashMap 也会发生故障。 与 TreeSet 和 TreeMap 类似。 所有这些数据结构都可以快速定位对象,因为每个对象的值决定了它的存储位置。 如果这些对象随后被修改,结构就会出错。

不可变对象作为集合元素和映射键更好,因为它们避免了这种复杂性。

如果您必须修改属于对象相等性一部分的字段,则需要先将其暂时从集合中删除,然后再添加。 或者,使用列表作为对象的主要容器,并仅在需要时构造一个临时集。

Block 对象是可变的吗?

HashSet 将其内容作为键存储在 HashMap 中,并以任意对象作为值。

根据这篇文章,

如果一个对象的 hashCode() 值可以根据它的状态改变,那么在基于散列的集合中使用这样的对象作为键时我们必须小心[强调我的] 以确保我们不允许它们的状态在它们被改变时改变。用作散列键。 所有基于散列的集合都假定对象的散列值在用作集合中的键时不会更改。 如果键的哈希码在集合中发生更改,则可能会出现一些不可预测且令人困惑的后果。 这在实践中通常不是问题——使用像 List 这样的可变对象作为 HashMap 中的键不是常见的做法。

openjdk 中contains的代码非常简单——它最终只调用HashMap.getEntry ,它使用哈希码并等于检查键是否存在。 我的猜测是您的错误在于认为该项目已经在集合中。 但是您可以通过直接在您发布的代码中声明一个Set并将项目添加到该集合中来轻松确认这是错误的。

尝试添加以下单元测试:

Set<Block> blocks = new HashSet<>();
blocks.add(thisBlock);
assertTrue(thisBlock.hashCode() == otherBlock.hashCode() && thisBlock.equals(otherBlock));
assertTrue(blocks.contains(otherBlock));

如果第一个断言通过而第二个断言失败,那么您就发现了 Java 中的一个错误。 我觉得这不太可能。

还要确保您有可用的 openjdk 源代码,以便您可以在调试时进入 Java 方法。 这样你就可以进入contains并准确检查它失败的地方。

另请注意,您的代码this.blocks.iterator().next()每次调用该函数时都会创建一个新的迭代器,然后返回迭代中的第一项。 换句话说,它选择集合中的第一项(请注意,这不是最不自然的顺序)。 如果您尝试按顺序遍历两个集合并比较值,那么这不是您的代码目前所做的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM