我有这个代码:
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”为true,但布尔“contains”为false。关于为什么2个对象的哈希值根据“equals”方法相等和相等的任何想法,但是不会在HashSet中包含相等的对象?
可能导致这种情况的一个可能原因是,如果您在将对象添加到集合之后以某种方式修改了对象,这些对象会影响它们的相等性和哈希码。这将导致集合出现故障,因为在哈希表的正确插槽中找不到与其新值对应的对象。
不可变对象可以提供更安全的集合元素和映射键,因为它们没有这种风险。
如果您绝对需要修改属于对象相等性的属性,则需要先将其暂时从集合中删除,否则丢弃该集合并随后组合新集合。
Block对象是否可变?
HashSet将其内容作为键存储在HashMap中,并以任意Object作为值。
如果一个对象的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()
都会创建一个新的迭代器,然后返回迭代中的第一个项。换句话说,它选择集合中的第一个项目(请注意,这不是自然顺序中的最小项目)。如果您尝试按顺序迭代这两个集并比较值,那么这不是您的代码目前所做的。