为什么重写 equals() 就一定要重写 hashCode() 方法?

参考回答

重写 equals() 时必须重写 hashCode(),是因为 equals()hashCode() 在 Java 中有一定的约定关系。如果两个对象根据 equals() 方法被认为是相等的,那么它们的 hashCode() 值也必须相等,否则会导致哈希容器(如 HashMapHashSet)的工作异常。

具体原因是:

  1. 哈希容器(如 HashMap)使用 hashCode() 方法来确定对象存储的位置。
  2. 如果只重写 equals(),而没有正确重写 hashCode(),可能导致逻辑上相等的两个对象被分配到不同的哈希桶中,违背了哈希容器的工作原理。

详细讲解与拓展

1. Java 中 equals()hashCode() 的约定

Java 对 equals()hashCode() 方法有以下规定:

  1. 如果两个对象根据 equals() 方法相等,则它们的 hashCode() 必须相等。
  2. 如果两个对象根据 equals() 方法不相等,则它们的 hashCode() 值可以相同(但这会影响性能,因为会增加哈希冲突的概率)。
  3. 如果没有重写 equals()hashCode()Object 类的默认实现会直接比较内存地址。

2. 哈希容器的工作原理

以下以 HashMap 为例,讲解哈希容器如何利用 hashCode()equals()

  1. 存储过程
  • 当调用 put(key, value) 时,HashMap 首先通过 key.hashCode() 计算哈希值,确定该键值对存储在哪个桶(bucket)中。

  • 如果桶中已经有元素,则会调用

    “`
    key.equals()
    “`

    来比较键是否相等:

    • 如果相等,则更新键对应的值;
    • 如果不相等,则发生哈希冲突,链表或红黑树解决冲突。
  1. 查询过程
  • 当调用 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() 的规则

  1. 同一个对象多次调用 hashCode() 方法,返回值必须相同。
  2. 如果两个对象根据 equals() 方法相等,则 hashCode() 值必须相同。
  3. 如果两个对象根据 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,以避免空指针异常。

发表回复

后才能评论