源码配合实践一文弄清楚为什么重写equals一定要重写hashcode
简介
本文会带各位从源码的角度以及实际应用案例探究为什么重写equals一定要重写hashcode。
实践
考虑以下场景,现在有一个学生管理系统,每个学生有身份证号,姓名,年龄以及性别四个属性。
package com.equalsAndHashCode;
import java.util.Objects;
/**
* 学生
*
* @author ez4sterben
* @date 2023/07/18
*/
public class Student {
private String id;
private String name;
private Integer age;
private String sex;
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
public Student(String id, String name, Integer age, String sex) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
在现实生活当中,难免会出现姓名年龄性别完全相同的两个人,如果我们要在数据中区分他们,那么就需要使用身份证号来区分。
那么我们想判断学生管理系统中的两个学生是否是同一个学生,是不是只需要判断身份证号是否相同就可以了?
那么接下来我们重写一下equals方法,来判断两个学生是否相同。
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Student student = (Student) o;
return id.equals(student.id);
}
再写一个测试类来查看是否生效。
package com.equalsAndHashCode;
/**
* 测试
*
* @author ez4sterben
* @date 2023/07/18
*/
public class Test {
public static void main(String[] args) {
Student student1 = new Student("100000202307181024","李华",1,"女");
Student student2 = new Student("100000202307181025","张三",1,"男");
System.out.println(student1.equals(student2));
student2.setId("100000202307181024");
System.out.println(student1.equals(student2));
}
}
运行结果如下:
可以看到我们重写的equals方法已经生效,只要身份证号不同那么这两个学生就是不同的。
到这里大家可能会想:测试成功了为什么还需要重写hashcode?
别着急,考虑以下场景:现在教师需要将所有的学生存入到数据集中,方便输出查看。
看到这个需求我们就可以分析一下,学校中的每个学生当然是不同的了,那么存储不同数据要使用的数据结构就应该是HashSet。
接下来我们实现这个需求
package com.equalsAndHashCode;
import java.util.HashSet;
import java.util.Set;
/**
* 测试
*
* @author ez4sterben
* @date 2023/07/18
*/
public class Test {
public static void main(String[] args) {
Student student1 = new Student("100000202307181024","李华",1,"女");
Student student2 = new Student("100000202307181025","张三",1,"男");
Set<Student> students = new HashSet<>();
students.add(student1);
students.add(student2);
System.out.println(students);
}
}
查看结果如下:
现在我们满足了教师的需求,但是现在问题来了,有一名学生在网络提交信息的时候把名字写错了,提交了多次数据。不过不用担心,我们使用的是HashSet,应该只有一份数据,不会出现多个相同id学生的问题。
我们让张三同学来承担一下这个责任
package com.equalsAndHashCode;
import java.util.HashSet;
import java.util.Set;
/**
* 测试
*
* @author ez4sterben
* @date 2023/07/18
*/
public class Test {
public static void main(String[] args) {
Student student1 = new Student("100000202307181024","李华",1,"女");
Student student2 = new Student("100000202307181025","张三",1,"男");
Student student3 = new Student("100000202307181025","张四",1,"男");
Set<Student> students = new HashSet<>();
students.add(student1);
students.add(student2);
students.add(student3);
System.out.println(students);
}
}
结果却是出人意料
我们的HashSet里居然有两个相等身份证号的同学出现了。而相同身份证号命名是判等才对的,HashSet不是无序不可重复的吗?
下面通过源码来带大家分析一下
源码解析
下面我们查看HashSet的源码看看到底为什么会发生这种情况。
首先我们调用了HashSet的构造方法
但他的内容是创建一个新的HashMap
原来HashSet是借助HashMap实现的,我们来看看他的add方法,通过idea中的Structure可以看到类的结构
到这里我们可以发现,这个add方法,调用的也是map的put方法。以传入的元素作为Key,PRESENT作为Value,而PRESENT其实是一个空对象
下面来看看put方法
从这里可以看出来,HashMap对于节点的判断其实是利用了hash函数,而这个hash函数是借助了传入的Key的Hash函数
所以说,如果我们只重写了equals没有重写hashcode,就会发生与期望结果不相符的情况。
接下来我们重写一下hashcode
@Override
public int hashCode() {
return Objects.hash(id);
}
老规矩,身份证号一样就行。
再次运行
这样就满足我们的预期了,只要身份证号一样,就认为这个学生已经存在了。