为什么重写 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,以避免空指针异常。