重写 equals 但未重写 hashCode:HashMap 存储数据丢失

45 阅读2分钟

一、Bug 场景

在一个基于 Java 的应用程序中,自定义了一个类并将其对象存储在 HashMap 中。为了确保对象在 HashMap 中的正确比较,开发人员重写了 equals 方法,但却忘记重写 hashCode 方法。结果在程序运行过程中,发现存储在 HashMap 中的某些对象丢失了,导致相关业务逻辑出现错误。

二、代码示例

自定义类(有缺陷)

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && name.equals(person.name);
    }

    // 未重写hashCode方法
}

测试代码

import java.util.HashMap;
import java.util.Map;

public class HashMapDataLossBugExample {
    public static void main(String[] args) {
        Map<Person, String> personMap = new HashMap<>();
        Person person1 = new Person("Alice", 30);
        Person person2 = new Person("Alice", 30);

        personMap.put(person1, "Person 1 Details");
        personMap.put(person2, "Person 2 Details");

        System.out.println("Map size: " + personMap.size());
        System.out.println("Expected value for person2: Person 2 Details, Actual: " + personMap.get(person2));
    }
}

三、问题描述

  1. 预期行为person1 和 person2 是内容相同的两个 Person 对象,按照 equals 方法的定义,它们应该被视为相等。在 HashMap 中,应该只存储一份相同内容的对象,并能通过任意一个对象获取到对应的详细信息。因此,personMap.size() 应该返回 1,且通过 person2 获取到的值应该是 "Person 2 Details"
  2. 实际行为personMap.size() 返回 2,并且通过 person2 获取到的值为 null。这是因为在 Java 中,HashMap 使用 hashCode 方法来确定对象在哈希表中的存储位置。如果两个对象通过 equals 方法比较相等,但它们的 hashCode 方法返回不同的值,HashMap 会将它们存储在不同的位置,从而导致逻辑上相同的对象被当作不同的对象处理。由于没有重写 hashCode 方法,person1 和 person2 的 hashCode 值不同(默认的 hashCode 方法基于对象的内存地址),尽管它们通过 equals 方法比较是相等的。

四、解决方案

  1. 重写 hashCode 方法:根据 equals 方法中用于比较的字段,合理地重写 hashCode 方法,确保相等的对象具有相同的哈希码。
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && name.equals(person.name);
    }

    @Override
    public int hashCode() {
        int result = name.hashCode();
        result = 31 * result + age;
        return result;
    }
}
  1. 使用 Objects.hashjava.util.Objects 类提供了 hash 方法,可以方便地生成哈希码,简化 hashCode 方法的编写。
import java.util.Objects;

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && name.equals(person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}