Java - `hashCode()` 和 `equals()` 为什么同时重新
·
hashCode() 和 equals() 是 Java 中两个紧密关联的核心方法,它们共同定义了对象的相等性语义和哈希行为。正确重写这两个方法对集合(尤其是 HashMap、HashSet 等基于哈希的集合)的正确性和性能至关重要。
一、基本定义
1. equals(Object obj)
- 作用:判断两个对象是否“逻辑相等”。
- 默认实现(来自
Object类):public boolean equals(Object obj) { return (this == obj); // 仅比较引用地址(即是否是同一个对象) }
2. hashCode()
- 作用:返回对象的哈希码(一个
int值),用于哈希表(如HashMap)快速定位存储位置。 - 默认实现(来自
Object类):public native int hashCode(); // 通常基于对象内存地址生成(不同 JVM 可能不同)
二、两者之间的契约(Java 官方规范)
《Effective Java》第 3 章强调:只要重写
equals(),就必须重写hashCode()!
Java 规范明确规定:
-
一致性:
在对象未被修改影响equals()比较的前提下,多次调用hashCode()必须返回相同值。 -
相等性约束:
- 如果
a.equals(b) == true,那么a.hashCode() == b.hashCode()必须成立。 - 反之不成立:
hashCode()相等,equals()不一定为true(哈希冲突)。
- 如果
-
不相等对象:
a.equals(b) == false时,hashCode()可以相同也可以不同(但不同可减少哈希冲突,提升性能)。
⚠️ 违反此契约会导致严重问题(见下文示例)。
三、为什么需要重写?—— 默认实现的问题
场景:自定义类用于 HashSet 或 HashMap 的 key
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 o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person p = (Person) o;
return age == p.age && Objects.equals(name, p.name);
}
}
问题演示:
Set<Person> set = new HashSet<>();
Person p1 = new Person("Alice", 20);
Person p2 = new Person("Alice", 20);
System.out.println(p1.equals(p2)); // true(逻辑相等)
set.add(p1);
set.add(p2);
System.out.println(set.size()); // 输出 2!❌ 应该是 1
原因分析:
p1和p2虽然equals()为true,但它们的hashCode()来自Object默认实现(基于内存地址),值不同。HashSet内部使用HashMap,先通过hashCode()定位桶(bucket),再用equals()比较。- 因为
hashCode()不同,p1和p2被放入不同的桶,equals()根本不会被调用 → 重复元素被错误添加。
💥 结论:不重写
hashCode()会导致基于哈希的集合行为异常!
四、如何正确重写?
✅ 正确做法:同时重写 equals() 和 hashCode()
import java.util.Objects;
class Person {
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
// 使用 Objects.hash 自动生成(推荐)
return Objects.hash(name, age);
// 或手动计算:
// return name != null ? name.hashCode() * 31 + age : age;
}
}
验证结果:
Set<Person> set = new HashSet<>();
set.add(new Person("Alice", 20));
set.add(new Person("Alice", 20));
System.out.println(set.size()); // 输出 1 ✅
五、重写原则与技巧
1. 参与 equals() 比较的字段,必须全部参与 hashCode() 计算
- 否则可能违反“相等对象哈希码必须相同”的契约。
2. 使用 Objects.hash()(Java 7+)
@Override
public int hashCode() {
return Objects.hash(field1, field2, field3);
}
- 自动处理
null值; - 使用标准算法(31 倍乘法)。
3. 不可变对象更安全
- 如果对象在加入集合后被修改,且修改影响
hashCode(),会导致对象“丢失”(无法被找到)。 - ✅ 建议:作为
Map的 key 或Set元素的对象应为不可变对象(如String、Integer)。
六、常见误区
| 误区 | 正确理解 |
|---|---|
“只要 equals() 返回 true 就行,hashCode() 无所谓” |
❌ 违反契约,哈希集合失效 |
“hashCode() 必须唯一” |
❌ 允许冲突(只是影响性能) |
“equals() 用 == 比较就行” |
❌ 逻辑相等 ≠ 引用相等 |
| “基本类型不用考虑” | ❌ 包装类(如 Integer)已正确重写,但自定义类必须自己处理 |
七、总结:为什么要重写?
| 场景 | 是否需要重写 |
|---|---|
| 对象仅用于局部变量、临时计算 | ❌ 不需要 |
对象要放入 HashSet、HashMap、Hashtable 等 |
✅ 必须同时重写 equals() 和 hashCode() |
对象要作为 Map 的 key |
✅ 必须重写 |
| 对象要进行逻辑相等比较(而非引用比较) | ✅ 至少重写 equals(),但通常也需要 hashCode() |
✅ 黄金法则:
“若重写equals(),必重写hashCode();反之,若不重写equals(),通常也不应重写hashCode()。”
正确实现这两个方法,是写出健壮、高效 Java 程序的基础。现代 IDE(如 IntelliJ IDEA、Eclipse)均可自动生成符合规范的代码,但理解其原理至关重要。
更多推荐




所有评论(0)