- 概述
- 不重写equals()会带来什么问题
- 不重写hashCode()会带来什么问题
- 总结
概述
-
equals()和hashCode()是java.lang.Object类的两个重要的方法,在实际应用中的类通常都需要重写这两个方法,那么究竟为什么要重写这两个方法呢?
-
首先我们先来看一看Object类中的这两个方法是怎么写的:
public native int hashcode();
public boolean equals(Object paramobject){
return(this == paramobject);
}
- 从源码看出,Object类默认的equals()方法比较的是两个对象的内存地址(对于两个对象而言,==比较的是两个对象的地址值,对于基本数据类型而言,==比较的是具体的数值);而hashcode()是本地方法(用C语言实现的方法),因为Java的内存是安全的,因此无法根据散列码得到对象的内存地址;所以在Object类中,hashcode()方法是根据对象的内存地址用哈希算法算出来的一个int值。
不重写equals()会带来什么问题
- 因此如果在一个类中不重写equals()方法,那么平时我们在代码中使用特别频繁的equals()方法直接就是拿你的对象的地址值去做比较的,而我们本意一般都是希望比较对象中实际存储的内容;
首先我们来定义一个类:
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = 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 String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试代码:
public static void main(String[] args) {
Person p1 = new Person("小马",12);
Person p2 = new Person("小马",12);
Person p3 = p2;
System.out.println(p1.equals(p2));//false
System.out.println(p2.equals(p3));//true
System.out.println(p1.equals(p3));//false
}
- 由于在Person类中并没有重写equals(),因此直接使用Object类中的equals()方法,比较的是地址值;因为p1与p2都是new 了一个person对象,因此指向堆内存中不同的区域,地址值也不一样;而p2将存储的地址值给了p3,p3与p2就同时指向堆内存中的同一块空间;
- 而我们在写代码的时候,本意一般都是希望比较的是Person对象中的具体值内容;如果我们在刚在的Person类中加入equals()方法;
@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 &&
Objects.equals(name, person.name);
}
再次运行刚才的测试代码,得到结果:
System.out.println(p1.equals(p2));//true
System.out.println(p2.equals(p3));//true
System.out.println(p1.equals(p3));//true
- 这个时候我发现由于p1,p2,p3指向的对象中存储的内容一致,因此equals()返回的结果也都是ture了;
不重写hashCode()会带来什么问题
- 到目前为止,小伙伴们可能会想,好像也并没有hashcode()什么事情啊;实际上,刚才的比较我们并没有用到hash算法,而hash算法在我们的代码中无处不在,为啥?举个例子,常用的集合中,基本上底层都是使用了Hash算法的,比如今天我们用HashSet来举个例子,看看不重写hashCode()会带来什么问题;
首先我们修改一下我们的测试代码:
Person p1 = new Person("小马",12);
Person p2 = new Person("小马",12);
Person p3 = new Person("小红",21);
Set set = new HashSet();
set.add(p1);
set.add(p2);
set.add(p3);
//结果:[Person{name='小马', age=12}, Person{name='小红', age=21}, Person{name='小马', age=12}]
System.out.println(set);
- HashSet集合是可以自动去重的,而去重原理跟Hash也有很大的关系;如果不重写hashCode()方法,我们看到在set中打印出了三条信息,而其中有2条是重复的,而我们希望它能根据内容进行去重,怎么做呢?
- 我们在Person类中重写一下HashCode()方法:
@Override
public int hashCode() {
return Objects.hash(name, age);
}
再次运行测试代码,得到结果:
//结果:[Person{name='小红', age=21}, Person{name='小马', age=12}]
System.out.println(set);
- 我们发现已经根据内容进行了去重;
- 具体流程可以理解为,添加p2的时候,add()方法会调用"p2"的hashcode方法计算哈希值(元素在集合底层数组中存储的位置),若哈希值是123456,在集合中找有没有123456这个哈希值的元素,发现有(哈希冲突),"p2"调用equals()方法和哈希值相同的元素进行比较,p2.equals(p1)返回true,内容一致,不再存储p2;
因此重写hashCode()的重要性不言而喻,而从另一个角度来讲,根据hashCode的规则,两个对象相等其哈希值一定相等;然而重写了equals,且p1.equals(p2)返回true,因此p1与p2的哈希值结果也应该相等才对,而如果不重写,让hashCode()是根据地址值去计算的哈希值,那么p1与p2的哈希值就会不一致,就违背了hashcode的规则;
总结
如果在类中不重写equals()方法,那么类对象在调用equals()方法的时候比较的就是地址值,我们希望在后续的对象比较中比较内容而不是地址值,就需要重写equals()方法;
如果不重写hashCode()方法,那么调用hashCode()方法的时候就是根据地址值计算出来的哈希值,如果希望在使用到哈希值的时候(比如,Set集合元素去重)是根据内容计算的哈希值而不是根据地址值计算的哈希值,就需要重写hashCode()方法;
当然,重写equals()与hashCode()方法是可以通过IDE自动生成;
当然,根据hashcode的规则,如果两个对象equals()比较为true,那么hashCode也应该一致,因此建议我们不仅要重写equals(),同时还应该重写hashCode()方法。
注意:本文归作者所有,未经作者允许,不得转载