Source code with practice article to figure out why rewriting equals must rewrite hashcode
Introduction
This article will take you from the perspective of source code and practical application cases to explore why rewriting equals must rewrite hashcode.
practice
Consider the following scenario. Now there is a student management system, and each student has four attributes: ID number, name, age and gender.
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;
}
}
In real life, it is inevitable that there will be two people with the same name, age and gender. If we want to distinguish them in the data, we need to use the ID number to distinguish them.
Then we want to judge whether the two students in the student management system are the same student, do we only need to judge whether the ID numbers are the same?
Then let's rewrite the equals method to determine whether two students are the same.
@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);
}
Write another test class to see if it works.
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));
}
}
The result of the operation is as follows:
You can see that our rewritten equals method has taken effect. As long as the ID numbers are different, the two students are different.
At this point, you may think: Why do you need to rewrite the hashcode if the test is successful?
Don't worry, consider the following scenario: Now the teacher needs to store all the students in the data set for easy output viewing.
Seeing this requirement, we can analyze it. Of course, each student in the school is different, so the data structure to be used to store different data should be HashSet.
Next we implement this requirement
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);
}
}
View the results as follows:
Now we have met the teacher's needs, but now there is a problem. A student wrote the wrong name when submitting information online and submitted data multiple times. But don't worry, we are using HashSet, there should be only one copy of data, and there will be no problem of multiple students with the same id.
Let Zhang San take this responsibility
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);
}
}
The result is unexpected
In our HashSet, there are actually two students with the same ID number. And the naming of the same ID number should be judged. Isn't HashSet disorderly and non-repeatable?
Let's analyze it through the source code
Source code analysis
Let's look at the source code of HashSet to see why this happens.
First we call the constructor of HashSet
But his content is to create a new HashMap
It turns out that HashSet is implemented with the help of HashMap . Let's take a look at its add method. You can see the structure of the class through the Structure in the idea
Here we can find that this add method also calls the put method of map. Use the incoming element as Key, PRESENT as Value, and PRESENT is actually an empty object
Let's take a look at the put method
It can be seen from here that HashMap actually uses the hash function to judge the nodes, and this hash function uses the Hash function of the incoming Key
So, if we only rewrite equals without rewriting hashcode, it will not match the expected result.
Next we rewrite the hashcode
@Override
public int hashCode() {
return Objects.hash(id);
}
As usual, the ID number is the same.
run again
This meets our expectations. As long as the ID number is the same, the student is considered to already exist.