为什么重写equals必须要重写hashCode
1.一些误区
- 重写了equals不重写hashcode并不会报错,只是逻辑上会出错。
- 并不是每次重写equals都需要重写hashcode,但推荐都写上。
2.为什么要重写equals
要理解重写hashcode的必要性,我们先来讲讲为什么要重写equals。原因也很简单,默认的equals方法不够看了,需要给它升
级一下。
先看看所有类的祖宗Object
类中equals方法的代码,这可以让我们理解为什么要重写equals。
public boolean equals(Object obj) {
return (this == obj);
}
可以看到,在Object
源码里,equals方法就是很简单的用 ==
来判断了一下。总所周知, ==
对类对象就是判断引用是否相等,
所以默认的equals的逻辑就是:如果需要判断的双方引用了相同的一个对象,那么就是相等的。看起来能满足我们的要求,可
是有的时候如果我们想让不同对象相同内容的两个类也算相等怎么办?比如我们想比较两个字符串对象,虽然它两里面的字
符都是一模一样的,可按照这样的逻辑,还是会判为不同,还好前辈们帮我们在String
类里重写了equals
方法,我们才能正常
比较。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
3.重写equals的小demo
上面讲了重写equals
的原因,我们先写个小demo来铺垫一下。
public class EqualsAndHashcode {
public static void main(String[] args) {
people people1 = new people();
people people = new people();
System.out.println(people.equals(people1));
}
}
class people{
private String name;
private int age;
public people(){
name = "李华";
age = 18;
}
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}
if (obj==null){
return false;
}
if(getClass() != obj.getClass()){
return false;
}
people other = (people) obj;
return Objects.equals(name,other.name)
&&age == other.age;
}
}
可以看到,对于两个具有相同初始化people对象,equals
是可以满足我们要求的。
上面的代码看似没有什么问题,我们想要的结果完美达到,但这只是因为还没遇到会使它犯错的情况:散列表。
4.散列表
先介绍一下散列表放入对象的原理,简单来说就像是给每个位置只能放一个货物的货架摆货:拿到一个货物,先通过
hashCode
找到它应该放的位置,如果这个位置是空的,就放上去;如果已经放了货物,就使用equals
比较一下放的货物和当
前货物是不是同一个,是同一个就不放了,不是同一个就想办法给它再找一个位置放。
为什么遇到散列表,上面的代码就会出错呢?原因就是因为哈希函数,既然我们想让两个属性一样的类对象视为相等,那么在
散列表中也应当如此。
那么上面的代码还能满足这个要求吗?
//在main方法加上以下代码
HashSet<test.people> set = new HashSet<>();
set.add(people);
set.add(people1);
System.out.println(set);
结果:
[test.people@677327b6, test.people@14ae5a5]
很明显,set
里面有两个元素,虽然我们这两个元素都是一样的,可set不这么认为。这样显然不是我们想看到的结果,他两明明就是相同,怎么还能给它添加进去呢。
导致这的罪魁祸首大家应该也猜到了,就是哈希函数的锅。先来看看Object
中的hashCode
是怎么样的:
public native int hashCode();
看不到没关系,我们可以打印这两个people
对象的hashCode
看看。
System.out.println(people.hashCode());
System.out.println(people1.hashCode());
结果:
1735600054
21685669
这说明在set
看来,这两个就是不同的,所以把它们都放进去也合情合理。
到了这里问题也就水落石出了,重写hahsCode
的必要性就是因为如果不重写就会导致散列表重复。
5 结论
因为Object
类是所有类的祖宗类,所以每个类都应该存在equals
和hashCode
,如果不重写,就是Object
类中的实现。当我们
对equals
进行重写的时候,有可能要放入哈希表也有可能不要,如果需要放入哈希表,那么就需要重写hashCode
来保证不会
重复放入。
5 结论
因为Object
类是所有类的祖宗类,所以每个类都应该存在equals
和hashCode
,如果不重写,就是Object
类中的实现。当我们
对equals
进行重写的时候,有可能要放入哈希表也有可能不要,如果需要放入哈希表,那么就需要重写hashCode
来保证不会
重复放入。