这是一个非常经典的Java面试题。重写 equals() 时必须重写 hashCode() 主要是为了维护 hashCode方法的通用约定,确保基于哈希的集合(如 HashMap、HashSet、Hashtable)能够正常工作。
根本原因:hashCode 方法的约定
在 Object类的规范中,hashCode()方法有以下重要约定:
- 一致性:在对象没有被修改的情况下,多次调用 hashCode() 应返回相同的值
- 相等性:如果两个对象通过 equals() 方法判断相等,那么它们的 hashCode 值也必须相等
- 不等性:如果两个对象通过 equals() 方法判断不相等,它们的 hashCode 不一定不同(但不同可以提高哈希表性能)
违反约定的后果
示例代码
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 只重写了 equals,没重写 hashCode
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person)) return false;
Person p = (Person) obj;
return this.name.equals(p.name) && this.age == p.age;
}
}
问题场景
public class Test {
public static void main(String[] args) {
Person p1 = new Person("张三", 25);
Person p2 = new Person("张三", 25);
System.out.println(p1.equals(p2)); // true(内容相同)
// 问题1:违反 hashCode 约定
System.out.println(p1.hashCode()); // 可能为 1023487453
System.out.println(p2.hashCode()); // 可能为 1651191114
// equals 返回 true,但 hashCode 不同!
// 问题2:哈希集合行为异常
Map<Person, String> map = new HashMap<>();
map.put(p1, "员工信息");
System.out.println(map.get(p1)); // "员工信息"(正常)
System.out.println(map.get(p2)); // null(异常!应该找到)
// 因为 HashMap 先通过 hashCode 找桶,桶不同就不会调用 equals
}
}
哈希集合的工作原理
以 HashMap 为例,它的查找逻辑是:
查找 key 的流程:
1. 先调用 key.hashCode() 计算哈希值
2. 根据哈希值确定桶(bucket)位置
3. 在桶中通过 equals() 方法比较键是否相等
关键点:如果 hashCode 不同,HashMap 会直接认为这是不同的键,根本不会调用 equals() 方法!
正确的做法
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Person)) return false;
Person p = (Person) obj;
return this.age == p.age &&
Objects.equals(this.name, p.name);
}
@Override
public int hashCode() {
// 使用相同的字段计算哈希值
return Objects.hash(name, age);
}
}
为什么不是反过来(重写 hashCode 必须重写 equals)?
虽然不常见,但如果只重写 hashCode() 而不重写 equals(),也会有问题:
- 两个不相等的对象可能有相同的 hashCode(哈希碰撞)
- 但集合能正确处理,因为会用 equals() 做最终判断
- 不过逻辑上可能不一致,因为 equals() 使用的是 Object 的默认实现(比较地址)
最佳实践
- 同时重写:重写 equals() 时,一定要同时重写 hashCode()
- 使用相同字段:用 equals() 中比较的字段来计算 hashCode
- 保持一致性:保证相等的对象有相等的哈希码
- 不可变性:用作哈希键的对象应该是不可变的,否则修改后会导致找不到
- 使用工具方法:推荐使用
Objects.hash()或 IDE 自动生成
总结
| 场景 | 只重写 equals | 同时重写 equals 和 hashCode |
|---|---|---|
| equals 比较 | 正确 | 正确 |
| hashCode 一致性 | 违反约定 | 符合约定 |
| HashMap/HashSet | 行为异常 | 正常工作 |
| 程序稳定性 | 潜在 bug | 可靠 |
核心原则:equals()定义了逻辑相等,hashCode()为这种逻辑相等提供哈希支持,两者必须保持逻辑一致,这是 Java 集合框架能够正确工作的基础。