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