核心原因是:两个相等的对象必须具有相同的哈希码(hashCode),否则会导致哈希表等基于哈希的集合类出现逻辑错误。
简单说,重写equals
时必须重写hashCode
,是为了保证 "逻辑相等的对象,在哈希表中也被当成同一个对象"。
打个比方:
equals
就像 "身份证",判断两个对象是不是同一个人;
hashCode
就像 "住址",告诉哈希表(如HashSet
)这个对象该存在哪里。
如果两个人身份证(equals
返回 true),但住址不同(hashCode
不同),哈希表就会把他们当成两个人,分开存放 —— 这显然是错的。
所以,只要重写equals
(定义了 "什么是同一个对象"),就得重写hashCode
(保证同一个对象的住址相同),否则哈希表会出乱子。
以下是详细描述:
1. 官方规范要求
Java 官方文档明确规定了equals()
和hashCode()
的关系:
- 如果两个对象通过
equals()
比较返回true
,则它们的hashCode()
必须返回相同的整数。 - 反之,若两个对象的
hashCode()
不同,则equals()
必须返回false
(但hashCode()
相同,equals()
可能不同,即哈希冲突)。
如果违反这一规范,使用哈希表(如HashMap
、HashSet
)时会出现不可预期的行为。
2. 哈希表的工作原理
哈希表(如HashMap
)的存储和查找依赖以下步骤:
- 计算对象的
hashCode()
,确定其在哈希表中的桶(Bucket)位置。 - 在对应桶中,通过
equals()
比较对象是否存在。
若只重写equals()
而不重写hashCode()
,会导致:
- 两个
equals()
返回true
的对象,可能被分配到不同的桶中。 - 哈希表会错误地认为它们是不同的对象,从而出现重复存储或查找失败。
3. 反例:只重写equals()
的问题
public class Person {
private String id;
public Person(String id) {
this.id = id;
}
// 只重写equals(),未重写hashCode()
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return id.equals(person.id);
}
public static void main(String[] args) {
Person p1 = new Person("123");
Person p2 = new Person("123");
System.out.println(p1.equals(p2)); // true(逻辑上相等)
HashSet<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println(set.size()); // 输出2(错误!应视为同一个对象)
}
}
问题分析:
p1
和p2
的equals()
返回true
,但由于未重写hashCode()
,默认使用Object
类的hashCode()
(基于内存地址),导致两者哈希码不同。HashSet
会将它们存储在不同桶中,误认为是两个不同的对象。
4. 正确实现:同时重写equals()
和hashCode()
public class Person {
private String id;
public Person(String id) {
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return id.equals(person.id);
}
// 重写hashCode(),与equals()保持一致
@Override
public int hashCode() {
return id.hashCode(); // 基于equals()中比较的字段计算哈希码
}
public static void main(String[] args) {
Person p1 = new Person("123");
Person p2 = new Person("123");
System.out.println(p1.equals(p2)); // true
System.out.println(p1.hashCode() == p2.hashCode()); // true
HashSet<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println(set.size()); // 输出1(正确)
}
}
修复原理:
hashCode()
基于equals()
中用于比较的id
字段计算,确保equals()
返回true
的对象一定有相同的哈希码,从而在哈希表中被正确识别为同一个对象。
5. 重写hashCode()
的原则
- 一致性:同一对象多次调用
hashCode()
应返回相同值(前提是对象未被修改)。 - 相等性:
equals()
返回true
的两个对象,hashCode()
必须相等。 - 分散性:
equals()
返回false
的对象,应尽量让hashCode()
不同(减少哈希冲突,提高哈希表效率)。
6. 实现技巧
- 基于
equals()
中使用的所有字段计算哈希码。 - 可通过
Objects.hash()
简化实现(自动处理null
):@Override public int hashCode() { return Objects.hash(id, name, age); // 传入所有参与equals比较的字段 }