在Java中,重写equals
方法的类通常也需要重写hashCode
方法,这是因为两者共同维护了对象的相等性契约。
1. 为什么需要同时重写 equals
和 hashCode
?
Java 的规范要求
根据 Java 官方文档规定:
- 规则一:如果两个对象通过
equals()
方法比较是相等的(a.equals(b) == true
),那么它们的hashCode()
必须返回相同的值。 - 规则二:如果两个对象的
hashCode()
不同,那么它们一定不相等(a.equals(b) == false
)。
如果违反这一规则,对象在使用哈希表(如 HashMap
、HashSet
)时会出现逻辑错误。
2. 不重写 hashCode
的后果
示例场景
假设有一个 Person
类,仅重写 equals
但未重写 hashCode
:
class Person {
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
// 未重写 hashCode()
}
问题演示
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);
// equals 返回 true,但 hashCode 不同(默认基于内存地址)
System.out.println(p1.equals(p2)); // true
System.out.println(p1.hashCode() == p2.hashCode()); // false
// 存入 HashSet
Set<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
// 两个对象都被保留,因为哈希码不同,HashSet认为它们是不同的
System.out.println(set.size()); // 输出 2(违背预期)
3. 哈希表的工作原理
哈希表(如 HashMap
、HashSet
)依赖 hashCode
快速定位对象存储的桶(Bucket):
- 插入对象时:根据
hashCode
确定桶的位置。 - 查找对象时:先通过
hashCode
找到桶,再通过equals
在桶内匹配对象。
如果 hashCode
不一致
- 即使
equals
返回true
,哈希表会将对象分配到不同的桶,导致无法正确匹配。 - 结果:对象重复存储,或无法找到已存在的对象。
4. 如何正确重写 hashCode
基本原则
hashCode
应基于参与 equals
比较的字段生成。例如:
@Override
public int hashCode() {
return Objects.hash(name, age); // 使用 Java 7+ 的 Objects.hash()
}
手动实现
@Override
public int hashCode() {
int result = 17;
result = 31 * result + name.hashCode();
result = 31 * result + age;
return result;
}
- 使用质数(如31)减少哈希冲突。
5. 总结
操作 | 不重写 hashCode 的风险 | 正确重写后的效果 |
---|---|---|
存入哈希表 | 相同对象被分配到不同桶,导致重复 | 相同对象分配到同一桶 |
查找对象 | 无法通过 hashCode 定位到正确桶 | 快速定位桶并匹配对象 |
契约一致性 | 违反 Java 规范,导致不可预测行为 | 符合规范,逻辑正确 |
6. 注意事项
- 不可变对象:如果对象作为哈希表的键,应确保其参与哈希计算的字段是不可变的,否则哈希值变化会导致无法检索。
- 性能优化:哈希函数应尽量避免冲突,但允许合理冲突(如
String
的哈希码可能重复)。
结论
重写 equals
时必须同时重写 hashCode
,这是为了确保对象在哈希表中行为一致,并遵守 Java 的相等性契约。忽略这一点会导致程序出现隐蔽的逻辑错误,尤其在依赖哈希结构的场景下。