equals()
和hashCode()
方法是java.lang.Object
类中的两个方法,所有Java类都会继承这两个方法。
本文解释了equals()函数和hashCode()函数的作用,以及它们之间的关系,为什么重写equals()就必须重写hashCode()函数。
equals()
大部分情况,我们自己定义的类会重写equals()
方法,比如:
class People{
private String name;
People() {}
People(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} else {
People p = (People)o;
return p.name.equals(this.name);
}
}
}
这时,如果我们创建两个name一样的People对象调用equals()
方法判断它们是否相等,就会返回true。
public class Main {
public static void main(String[] args) {
People p1 = new People("Bob");
People p2 = new People("Bob");
System.out.println(p1.equals(p2));
}
}
true
hashCode()
Object
中的hashCode()
方法是native方法,也就是用C或者C++语言实现的,该方法通常用来将对象的内存地址转换为整数之后返回,返回的整数称为哈希码。
举个例子:
public class Main {
public static void main(String[] args) {
People p1 = new People("Bob");
People p2 = new People("John");
System.out.println(p1 + " -> " + p1.hashCode());
System.out.println(p2 + " -> " + p2.hashCode());
}
}
People@776ec8df -> 2003749087
People@16b98e56 -> 381259350
当然这里的People@776ec8df
并不是真正的内存地址,只是个标识。
学过数据结构哈希表的同学都知道,大部分情况下,不同的输入哈希函数会计算出不同的值,少数情况下,不同的输入哈希函数也可能得到相同的值;另外,相同的输入哈希函数计算出的值一定相同。也就是说,不同的对象调用hashCode()
少数情况下可能得到相同的哈希码。
equals()与hashCode()的关系
我们平时会使用类似HashSet
、HashMap
这样的数据结构来编写程序,那么在插入一个数据的时候它们是如何判断数据是否相等的?这里以HashSet
为例。
插入一个对象时,HashSet
会先调用对象的hashCode()
函数计算哈希码,如果哈希码与所有已加入集合的对象都不同,那么新加入的对象一定与原有对象不同,直接加入集合中;如果哈希码与某个已加入集合的对象哈希码相同,这两个对象也可能并不相同,再调用equals()
来判断它们是不是真的相同,不同再加入集合。
这种实现方式大大减少了equals()
函数的调用次数,提高了执行速度。
为什么重写equals()就必须重写hashCode()
打开Java官方文档查看java.lang.Object.hashCode()
方法的说明
在官方文档中硬性规定了:
- If two objects are equal according to the
equals(Object)
method, then calling thehashCode
method on each of the two objects must produce the same integer result.
即:如果两个对象调用equals()
方法返回true
,那么这两个对象的hashCode()
方法必须返回值相同的整数值。
我们依然以上述提到的People
类为例,我们已经实现了equals()
方法,但是并没有实现hashCode()
方法,就会出现这种情况:两个对象调用equals()
函数相等,但是它们的hashCode()
并不相等。
public class Main {
public static void main(String[] args) {
People p1 = new People("Bob");
People p2 = new People("Bob");
System.out.println(p1.equals(p2));
System.out.println(p1.hashCode() == p2.hashCode());
}
}
true
false
所以我们必须重写hashCode()
函数使得它们也相等。这是笔者随意给出一种重写方式:
@Override
public int hashCode() {
return name.hashCode();
}
再次执行Main
类中的main()
方法,结果就满足了官方的规定:
true
true
那么问题又来了,为什么官方要这样规定?
本文前部分已经解释了hashCode()
函数的作用,相同的输入哈希函数计算出的值一定相同。既然我们重写了equals()
方法认为这两个地址不同的对象逻辑上相同,那么两个对象计算出的哈希码也应该是相同的。如果我们不重写hashCode()
,那么这两个逻辑上相同的对象在插入类似HashSet
这样的数据结构中时,因为它们的地址不同,HashSet
会认为它们是不同的对象而重复插入,这不是我们想要的结果,因为我们认为它们是相同的。
例如,两个People
对象,但它们其实是同一个人Bob,如果不重写hashCode()
函数,HashSet
就会重复插入造成无法预料的后果。
People p1 = new People("Bob");
People p2 = new People("Bob");