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 中,以任意对象作为值。
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.
我有这个代码:
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 中,以任意对象作为值。
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.