HashSet.contains returns 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" 为假。关于为什么 2 个对象的哈希值根据 "equals" 方法会相等和相等,但不会在 HashSet 中包含相等对象的任何想法?

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() 会在每次调用函数时创建一个新迭代器,然后 returns 迭代中的第一项。换句话说,它会选择集合中的第一项(请注意,这不是自然顺序最少的)。如果您试图按顺序遍历这两个集合并比较值,那么您的代码目前不会这样做。

如果在将对象添加到 HashSet 后 以影响对象相等性和哈希码的方式修改对象,就会发生此问题。该集合将出现故障,因为未在与其新值相对应的散列 table 的正确槽中找到对象。

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

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

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

Block 对象是可变的吗?

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

As per this article,

If an object's hashCode() value can change based on its state, then we must be careful when using such objects as keys in hash-based collections [emphasis mine] to ensure that we don't allow their state to change when they are being used as hash keys. All hash-based collections assume that an object's hash value does not change while it is in use as a key in the collection. If a key's hash code were to change while it was in a collection, some unpredictable and confusing consequences could follow. This is usually not a problem in practice -- it is not common practice to use a mutable object like a List as a key in a HashMap.