为什么重写 equals() 就一定要重写 hashCode() 方法?
参考回答
重写 equals()
时必须重写 hashCode()
,是因为 equals()
和 hashCode()
在 Java 中有一定的约定关系。如果两个对象根据 equals()
方法被认为是相等的,那么它们的 hashCode()
值也必须相等,否则会导致哈希容器(如 HashMap
、HashSet
)的工作异常。
具体原因是:
- 哈希容器(如
HashMap
)使用hashCode()
方法来确定对象存储的位置。 - 如果只重写
equals()
,而没有正确重写hashCode()
,可能导致逻辑上相等的两个对象被分配到不同的哈希桶中,违背了哈希容器的工作原理。
详细讲解与拓展
1. Java 中 equals()
和 hashCode()
的约定
Java 对 equals()
和 hashCode()
方法有以下规定:
- 如果两个对象根据
equals()
方法相等,则它们的hashCode()
必须相等。 - 如果两个对象根据
equals()
方法不相等,则它们的hashCode()
值可以相同(但这会影响性能,因为会增加哈希冲突的概率)。 - 如果没有重写
equals()
和hashCode()
,Object
类的默认实现会直接比较内存地址。
2. 哈希容器的工作原理
以下以 HashMap
为例,讲解哈希容器如何利用 hashCode()
和 equals()
:
- 存储过程:
- 当调用
put(key, value)
时,HashMap
首先通过key.hashCode()
计算哈希值,确定该键值对存储在哪个桶(bucket)中。 -
如果桶中已经有元素,则会调用
“`
key.equals()
“`来比较键是否相等:
- 如果相等,则更新键对应的值;
- 如果不相等,则发生哈希冲突,链表或红黑树解决冲突。
- 查询过程:
- 当调用
get(key)
时,HashMap
会先根据key.hashCode()
找到桶的位置,然后依次遍历桶中的元素,调用key.equals()
确定是否是目标键。
如果 hashCode()
和 equals()
不一致,会导致的问题:
- 如果两个对象的
equals()
返回true
,但hashCode()
不相等,它们会被分配到不同的桶中,导致get()
查询不到正确的值。 - 如果两个对象的
hashCode()
相等,但equals()
返回false
,则会导致哈希冲突增多,影响性能。
3. 举例说明
错误示例:重写 equals()
,但未重写 hashCode()
:
class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return name.equals(person.name);
}
}
public class Main {
public static void main(String[] args) {
HashSet<Person> set = new HashSet<>();
Person p1 = new Person("Alice");
Person p2 = new Person("Alice");
set.add(p1);
System.out.println(set.contains(p2)); // 输出 false,应该是 true
}
}
- 在上述代码中,虽然
p1.equals(p2)
返回true
,但它们的默认hashCode()
不同,因此被分配到不同的桶中,导致HashSet
无法识别它们是同一个对象。
正确示例:同时重写 equals()
和 hashCode()
:
class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return name.equals(person.name);
}
@Override
public int hashCode() {
return name.hashCode(); // 使用 name 的 hashCode 作为对象的 hashCode
}
}
public class Main {
public static void main(String[] args) {
HashSet<Person> set = new HashSet<>();
Person p1 = new Person("Alice");
Person p2 = new Person("Alice");
set.add(p1);
System.out.println(set.contains(p2)); // 输出 true
}
}
4. 重写 hashCode()
的规则
- 同一个对象多次调用
hashCode()
方法,返回值必须相同。 - 如果两个对象根据
equals()
方法相等,则hashCode()
值必须相同。 - 如果两个对象根据
equals()
方法不相等,则hashCode()
值尽量不同,减少哈希冲突。
5. 扩展知识:hashCode()
的实现技巧
hashCode()
的常见实现方式是根据对象的属性值计算哈希值。例如:
@Override
public int hashCode() {
int result = 17; // 任意非零常数
result = 31 * result + (name == null ? 0 : name.hashCode());
return result;
}
31
是一个常用的素数,能够有效减少冲突。- 属性为
null
时,赋值为 0,以避免空指针异常。