[英]Should hashCode() only use the subset of immutable fields of those used in equals()?
情况
我需要覆盖equals()
,因为我建议我也使用相同的字段覆盖hashCode()
方法。 然后,当我看着一个只包含一个对象的集合时,我得到了令人沮丧的结果
set.contains(object)
=> false
而
set.stream().findFirst().get().equals(object)
=> true
我现在明白了,这是因为在将object
添加到set
之后对object
所做的更改再次更改了其hashCode。 然后contains
查找错误的键并找不到该object
。
我对实施的要求是
equals()
Collections
或Maps
安全地使用这些对象,例如灰烬HashSet
。 这违反了惯例
equals()
和hashCode()
应该使用相同的字段以避免意外(如下所述: https : //stackoverflow.com/a/22827702 )。 题
仅使用equals()
中使用的字段子集计算hashCode()
而不是全部使用是否存在任何危险?
更具体而言,这将意味着: equals()
使用了编号的对象,而字段的hashCode()
只使用了在用于这些领域equals()
和是不可变的 。
我认为这应该没问题,因为
HashSet
找到。 相关帖子帮助我理解了我的问题,但没有解决方法: 在Java中覆盖equals和hashCode时应该考虑哪些问题? 和equals和hashcode的不同字段
合同确实会实现。 合同强制.equal()
对象始终具有相同的.hashCode()
。 相反的情况不一定是真的,我想知道一些人和IDE的痴迷恰好适用于这种做法。 如果所有可能的组合都可以,那么你会发现完美的哈希函数。
顺便说一句,IntelliJ在生成hashCode时提供了一个很好的向导,并且通过分别处理这两个方法并允许区分您的选择来提供相同的向导。 显然,相反,又名提供在更多领域hashCode()
和更少的领域中equals()
违反约定。
对于HashSet
和类似的集合/映射,使用hashCode()
只使用equals()
方法中的字段子集是一种有效的解决方案。 当然,您必须考虑哈希码有效减少地图中的冲突。
但请注意,如果要使用TreeSet
等有序集合,问题就会出现。 然后你需要一个永远不会给“不同”对象发生冲突(返回零)的比较器,这意味着该集合只能包含一个碰撞元素。 你的equals()
描述意味着存在多个只在可变字段中不同的对象,然后你输了:
所以我强烈建议再次考虑你的对象类的平等和可变性的概念。
这对我来说完全有效。 假设你有一个Person
:
final int name; // used in hashcode
int income; // name + income used in equals
name
决定条目的位置(想想HashMap
)或选择哪个桶。
你把一个Person
作为Key
放在HashMap
:根据hashcode
它会进入某个桶,例如第二个。 您升级income
并在地图中搜索该Person
。 根据hashcode
它必须在第二个桶中,但根据equals
它不存在:
static class Person {
private final String name;
private int income;
public Person(String name) {
super();
this.name = name;
}
public int getIncome() {
return income;
}
public void setIncome(int income) {
this.income = income;
}
public String getName() {
return name;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object other) {
Person right = (Person) other;
return getIncome() == right.getIncome() && getName().equals(right.getName());
}
}
并测试:
HashSet<Person> set = new HashSet<>();
Person bob = new Person("bob");
bob.setIncome(100);
set.add(bob);
Person sameBob = new Person("bob");
sameBob.setIncome(200);
System.out.println(set.contains(sameBob)); // false
您认为缺少的是hashcode
决定一个条目所在的桶(该桶中可能有许多条目),这是第一步,但是equals
决定它是否合适,是一个相等的条目。
您提供的示例完全合法; 但你链接的那个是另一种方式 - 它在hashcode
使用更多的字段,因此它是错误的。
如果您了解这些细节,那么第一个哈希码用于了解Where和Entry 可能驻留的位置,并且只有稍后所有这些(来自子集或存储桶)都试图通过equal
方式找到 - 您的示例才有意义。
hashCode()
可以使用equals()
使用的字段的子集,尽管它可能会给你一点性能下降。
您的问题似乎是由于修改了对象,同时仍然在集合内部,以改变hashCode()
和/或equals()
。 无论何时将对象添加到HashSet(或作为HashMap中的键),都不得随后修改equals()
和/或hashCode()
使用的该对象的任何字段。 理想情况下, equals()
使用的所有字段都应该是final
。 如果它们不可能,则必须将它们视为最终,同时对象在集合中。
TreeSet / TreeMap也是如此,但适用于compareTo()
使用的字段。
如果您确实需要修改equals()
使用的字段(或者在TreeSet / TreeMap的情况下通过compareTo()
),您必须:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.