一文让你搞懂:为什么重写equals方法的时候要同时重写hashcode方法
😀大家好!我是向阳🌞,一个想成为优秀全栈开发工程师的有志青年!
📔今天来说一说为什么重写equals方法的时候要同时重写hashcode方法。
hashCode
hashCode即散列码,hashCode方法返回对象的哈希码,返回值是int类型,是一个整数。hashCode主要用于一些支持哈希表数据结构的集合,如HashMap、HashSet等,用来快速确定对象的存储位置,好的hashCode方法应该生成均匀的散列码。
默认会根据对象的内存地址生成哈希码,如下图所示。
重写hashCode方法的基本规则
- 如果两个对象的equals方法判断相等,则他们的hashCode方法一定相等。
- 如果两个对象的equals方法不相等,那他们的hashCode方法不必不相等。
- 同一个对象多次调用 hashCode 方法,必须返回相同的值。
equals
equals方法用于判断两个对象是否相等,默认实现是使用 == 来比较两个对象的内存地址是否相等,如下图所示。
我们可以在类中通过重写equals方法来定义自己的判断是否相等逻辑,这也是为了现实生活中一些场景的合理性。
重写equals方法的基本规则
五大规则:
- 自反性:对于任何非空对象引用 x,x.equals(x) 必须为true。
- 对称性:对于任何非空对象引用 x 和 y,x.equals(y) 应当等于 y.equals(x)。
- 传递性:如果 x.equals(y) == true 且 y.equals(z) == ture,那么x.equals(z) == ture。
- 一致性:只要对象未发生改变,多此调用 x.equals(y) 结果应该一致。
- 对于null:对于任何非空对象引用 x,x.equals(null),必须返回false。
两者之间的关系
-
如果两个对象根据equals方法判断相等,则他们的hashCode值一定相等,即 a.equals(b)==true,则a.hashCode()==b.hashCode()。
-
如果两个对象的hashCode值相等,但是他们的equals不一定相等。
我们一定要遵循上述两个规则,否则在基于哈希表的集合中会出现错误,HashMap、HashSet这些集合内部依赖对象的 hashCode 方法和 equals 方法来确定元素的存储位置,如果没有正确重写这两个方法,则集合无法正确判断对象的相等性。
只重写equals方法
我们来试一试只重写equals方法会出现什么问题呢?我们先来写一个Person类,只重写equals方法。
Person类:
注意:String类的equals类内部已经经过重写了,比较的是两个字符串的值是否相等,而不是比较的地址值。
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
Person person = (Person) obj;
return person.name.equals(this.name) && person.age == this.age;
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
我们使用HashSet来进行测试,HashSet的特性就是去重,向HashSet插入两个属性值一模一样的Person类,来看看会插入一个还是两个呢。
测试类:
public class Test {
public static void main(String[] args) {
HashSet<Person> hashSet = new HashSet<>();
Person person1 = new Person();
person1.setName("张三");
person1.setAge(18);
Person person2 = new Person();
person2.setName("张三");
person2.setAge(18);
System.out.println(person1.equals(person2));
hashSet.add(person1);
hashSet.add(person2);
for (Person person : hashSet) {
System.out.println(person.toString());
}
}
}
打印结果:
从上述的打印结果可以看出来,已经出现了严重的问题,我们插入两个一模一样的对象属性,却都插入进去,没有去重。
只重写hashCode方法
把上述的hashCode方法改成下面这个,注释掉equals方法。
@Override
public int hashCode() {
return Objects.hash(name, age);
}
测试类代码不变,我们可以看到,结果还是如此,HashSet插入了两个一模一样的元素。
源码分析出现上述的问题
我们追踪到HashSet的 add 方法中,由于我们上面的Person类没有重写hashCode方法,我们这里计算hash值就是调用的Object的hashCode方法。
接着我们进入到核心方法 putVal 中,我们可以看到,判断条件都是先判断hash值是否相等,如果相等才进行判断equals方法是否相等。
所以我们在判断的时候,hash值不相等,直接返回的就是false,不会进行判断equals方法是否相等,所以添加第二个Person实例对象的时候,由于hash值不相等,直接插入到HashSet当中了。
如果我们没有重写equals方法,在判断key.equals(k)的时候,调用的是Object的equals方法,两个对象的内存地址不相同,判断的返回结果肯定是false,在相同key下值不会被覆盖,而是添加了进来。
两个方法都进行重写
Person类完整代码:
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
Person person = (Person) obj;
return person.name.equals(this.name) && person.age == this.age;
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
这个时候我们运行测试类,可以发现HashSet中只有一个值。
总结
hashCode 和 equals 两个方法是用来协同判断两个对象是否相等的,如果在重写 equals方法 时,不重写 hashCode方法,或者只重写 equals 方法,不重写 hashCode 方法,就会导致在基于哈希表的数据结构进行数据操作时出现一些问题,例如将两个相等的自定义对象存储在 Set 集合时,就会出现异常,所以我们在重写 equals 方法的同时也要重写 hashCode 方法。
——👦[作者]:向阳256
——⏳[更新]:2025.5.14
——🥰本人技术有限,如果有不对指正需要更改或者有更好的方法,欢迎到评论区留言。如果文章对您有帮助,请观众老爷您赏个赞吧。