在 Java 中,Object 类的 hashCode()equals(Object obj) 方法是两个非常关键的方法,它们在集合类(如 HashMapHashSetHashtable 等)中起着核心作用。为了确保这些集合类的正确行为,重写 hashCode() 时必须遵循一定的规范,并且要与 equals() 方法保持一致性


一、Object 类中默认行为

public boolean equals(Object obj) {
    return (this == obj);
}

public native int hashCode();
  • equals():默认比较引用地址(即是否是同一个对象)。
  • hashCode():默认返回一个基于对象内存地址的整数(由 JVM 实现,通常与内存地址相关)。

二、重写 hashCode() 的规范(来自官方文档)

根据 Java 官方文档,重写 hashCode() 必须遵守以下三条基本原则

✅ 规范 1:一致性(Consistency)

在一个对象的生命周期内,只要用于 equals 比较的字段没有改变,hashCode() 必须始终返回相同的整数

// 正确示例
public int hashCode() {
    return Objects.hash(name, age); // 只依赖不变或稳定字段
}

⚠️ 注意:不要在 hashCode() 中使用可变字段(如 lastModifiedTime),否则会导致 HashMap 中的对象“找不到”。


✅ 规范 2:相等对象必须有相同哈希码(Consistent with equals)

如果两个对象通过 equals(Object) 方法比较是相等的(即 a.equals(b) == true),那么它们的 hashCode() 必须返回相同的值

这是最重要的一条!否则会导致集合类(如 HashMap)失效。

// ❌ 错误示例
public class Person {
    private String name;

    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person p = (Person) o;
        return Objects.equals(name, p.name);
    }

    public int hashCode() {
        return new Random().nextInt(); // 每次返回不同值 —— 严重错误!
    }
}

即使 name 相同,hashCode() 不同 → 在 HashSet 中可能被当作两个不同对象 → 重复添加!


✅ 规范 3:不相等对象尽量有不同的哈希码(Performance Recommendation)

如果两个对象不相等(a.equals(b) == false),它们的 hashCode() 尽量返回不同的值,以减少哈希冲突,提高哈希表性能。

这不是强制要求,但影响性能。理想情况下,哈希函数应“均匀分布”。


三、hashCode()equals() 的一致性原则

“相等的对象必须有相同的哈希码”
即:
如果 a.equals(b) == true,那么 a.hashCode() == b.hashCode() 必须为 true

🚨 反例:不一致的后果

Person p1 = new Person("Alice");
Person p2 = new Person("Alice");

System.out.println(p1.equals(p2));     // true(假设 name 相同即相等)
System.out.println(p1.hashCode());     // 123
System.out.println(p2.hashCode());     // 456(假设只对 age 做 hash)

Set<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);

System.out.println(set.size());        // 输出 2!但逻辑上应为 1

❌ 问题:HashSet 使用 hashCode() 定位桶,再用 equals() 判断是否重复。哈希码不同 → 放入不同桶 → 被当作两个对象!


四、如何正确重写?

✅ 推荐做法:使用 Objects.hash()

import java.util.Objects;

public 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);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    // toString() 建议也重写
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

✅ 手动实现(了解原理)

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + name.hashCode();
    result = 31 * result + age;
    return result;
}
  • 初始值通常为 17(质数)
  • 每个字段用 31 * result + fieldHash,31 是质数且易于 JVM 优化

五、注意事项

项目 建议
🛑 不要使用可变字段 lastLoginTime,否则对象放入 HashSet 后修改字段,可能导致无法查找
✅ 使用 @Override 注解 防止拼写错误,确保正确覆盖
equals() 中先判断 null 和类型 避免 ClassCastException
✅ 使用 IDE 或 Lombok 自动生成 如 IntelliJ IDEA 的 Generate > equals() and hashCode(),或使用 @Data 注解(Lombok)
// 使用 Lombok(简洁安全)
@Data
public class Person {
    private String name;
    private int age;
}

六、最佳实践总结

原则 说明
✅ 同时重写 equalshashCode 两者必须“成对出现”
✅ 保持一致性 equals 相等 → hashCode 相等
✅ 基于不可变字段 保证哈希码稳定
✅ 使用 Objects.hash() 简洁、安全、高效
✅ 避免性能陷阱 哈希冲突过多会退化为链表,影响性能

🔚 结论

只要重写了 equals(),就必须重写 hashCode(),并且保证:
a.equals(b)true 时,a.hashCode() == b.hashCode() 也必须为 true