为什么重写 equals()就必须要同时重写 hashCode()?

这是一个非常经典的Java面试题。重写 equals() 时必须重写 hashCode() 主要是为了维护 hashCode方法的通用约定,确保基于哈希的集合(如 HashMap、HashSet、Hashtable)能够正常工作。

根本原因:hashCode 方法的约定

Object类的规范中,hashCode()方法有以下重要约定:

  1. 一致性:在对象没有被修改的情况下,多次调用 hashCode() 应返回相同的值
  2. 相等性如果两个对象通过 equals() 方法判断相等,那么它们的 hashCode 值也必须相等
  3. 不等性:如果两个对象通过 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(),也会有问题:

  1. 两个不相等的对象可能有相同的 hashCode(哈希碰撞)
  2. 但集合能正确处理,因为会用 equals() 做最终判断
  3. 不过逻辑上可能不一致,因为 equals() 使用的是 Object 的默认实现(比较地址)

最佳实践

  1. 同时重写:重写 equals() 时,一定要同时重写 hashCode()
  2. 使用相同字段:用 equals() 中比较的字段来计算 hashCode
  3. 保持一致性:保证相等的对象有相等的哈希码
  4. 不可变性:用作哈希键的对象应该是不可变的,否则修改后会导致找不到
  5. 使用工具方法:推荐使用 Objects.hash()或 IDE 自动生成

总结

场景只重写 equals同时重写 equals 和 hashCode
equals 比较正确正确
hashCode 一致性违反约定符合约定
HashMap/HashSet行为异常正常工作
程序稳定性潜在 bug可靠

核心原则equals()定义了逻辑相等,hashCode()为这种逻辑相等提供哈希支持,两者必须保持逻辑一致,这是 Java 集合框架能够正确工作的基础。


作 者:南烛
链 接:https://www.itnotes.top/archives/1051
来 源:IT笔记
文章版权归作者所有,转载请注明出处!


上一篇
下一篇