吊打面试官系列:说说hashCode和equals方法
首先我们需要知道hashCode
方法和equals
方法都是属于Object类的方法。既然属于Object中public修饰的方法,那言外之就是所有对象默认都有这两个方法,只是有时候有的对象已对这两个方法进行了重写。
hashCode
方法是返回一个对象的hash值(int类型),利用对象地址生成一个int类型的数。
equals方法是比较两个对象是否为同一个对象。
两者关系
从本质上来讲,两者是完全能没有什么关系的。但是在某些使用场景下,两者关系非常不一般。
什么场景呢?
比如说作为HashMap
、Hashtable
等散列表的key的时候,就是先比较key的hash值,相等再使用equals比较。
关于这个题目网上有很多文章:
这是百度的时候放在第一篇的文章。
谁说equals相等,hashCode
就一定相等?
下面我来证明一下
/**
* 欢迎关注公众号:java后端技术全栈
*
* @author 田维常
* @date 2020/11/16 14:17
*/
public class User {
private int id;
private int age;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//看看我的equals方法有问题吗,完全没毛病
//当三个属性相等我们就可以认为是同一个人了
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (this.getClass() != obj.getClass()) {
return false;
}
User user = (User) obj;
if (this.getAge() == user.getAge() &&
this.getId() == user.getId() &&
this.getName().equals(user.getName())) {
return true;
}
return false;
}
//hashcode我们使用默认的
@Override
public int hashCode() {
return super.hashCode();
}
}
写写个测试类
public class Test {
public static void main(String[] args) {
User user1=new User();
user1.setAge(22);
user1.setId(1);
user1.setName("老田");
User user2=new User();
user2.setAge(22);
user2.setId(1);
user2.setName("老田");
System.out.println(user1.equals(user2));
System.out.println("user1 hashcode="+user1.hashCode());
System.out.println("user2 hashcode="+user2.hashCode());
}
}
运行测试类
啪啪打脸,你还敢网上随便找答案吗?
在两个方法都没重写的情况下,如果我们想用它作为散列表的key,那么就得确保equals为true的情况下,hashCode
一定相等。
这里引入一个面试题:我们可以自定义HashMap
的key类吗?
答案是:可以
怎么自定义呢?
像我们上面的User类如果用来作为HashMap
的key明显不行。因为HashMap
是先使用key的hash值去查找对应的table下表,再通过key的hashCode
进行比较,相等的话再比较equals是不是相等。
我们使用上面的User来试试
public class Test {
public static void main(String[] args) {
User user1=new User();
user1.setAge(22);
user1.setId(1);
user1.setName("老田");
User user2=new User();
user2.setAge(22);
user2.setId(1);
user2.setName("老田");
Map<User,String> map = new HashMap<>();
map.put(user1,"老田1");
map.put(user2,"老田2");
for (Map.Entry<User,String> entry:map.entrySet()){
System.out.println(entry.getValue());
}
}
}
代码运行结果
所以这是不行的,因为user.equasl(user2)
是相等的。
关于hashCode
方法如何重写,只要满足,两个对象equals相等时,hashCode
相等就行。
看看大佬们是如何写的。
Integer中hashCode
方法,返回的hashCode
就是对应的值。
@Override
public int hashCode() {
return Integer.hashCode(value);
}
public static int hashCode(int value) {
return value;
}
比如说:
Integer = 20;
返回的hashCode
=20;
再来看看String类
public int hashCode() {
//hash值默认为0
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
//遍历字符串中每个字符
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
按照大佬们的玩法,我们也可以模仿着写一个。
自定义hashCode方法
我们把我们上面的User类中的hashCode
方法改造一下
@Override
public int hashCode() {
int result = 0;
result = result * 31 + name.hashCode();
result = result * 31 + age;
result = result * 31 + id;
return result;
}
再次运行测试类
第二次的put就把第一次put的"老田1"给覆盖了。
上面hashCode
为什么这么写呢?
这段描述摘抄自effective java
给我们的建议:
1.把某个非零的常数值,比如说0(一个你喜欢的数字),保存在一个名为result的int类型的变量中.
2.对于对象中每个关键域(指equals方法中涉及的每个域),完成以下步骤:
a.为该域计算int类型的散列码c:
i.如果该域是boolean类型,则计算(f?1:0)
ii.如果该域是byte,char,short或者int类型,则计算(int)f.
iii.如果该域是long类型,则计算(int)(f^(f>>>32)).
iv.如果该域是float类型,则计算Float.floatToIntBits(f).
v.如果该域是double类型,则计算Double.doubleToLongBits(f),然后按照步骤2.a.iii,为得到的long类型值计算散列值.
vi.如果该域是一个对象引用,并且该类的equals方法通过递归地调用equals的方式来比较这个域,则同样为这个域递归地调用hashCode.如果需要更加复杂的比较,则为这个域计算一个"范式",然后针对这个范式调用hashCode.如果这个域的值为null,则返回0(或者其他某个常数,但通常是0).
vii.如果该域是一个数组,则要把每一个元素当做单独的域来处理.也就是说,递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据步骤2.b中的做法把这些散列值组合起来.如果数组域中的每个元素都很重要,可以利用发行版本1.5中增加的其中一个Arrays.hashCode方法.
b.按照下面的公式,把步骤2.a中计算得到的散列码c合并到result中:
result = 31 * result +c;
3.返回result
从这里我们也得出一个结论:
hashCode
方法重写也是有技巧的,不是随便乱写就可以满足的,所以重写的时候一定要慎重。
总结
equals是用来比较对象是否相等的,相等的条件可以自定义,可以定义某个人是猫或狗都可以。
hashCode
主要是用在散列表中便于查找存放的位置。
同一个对象如果没有重写equals和hashCode
方法时,则equals相等,hashCode
也相等。
如果重写了那就啥都不说了,就看你是怎么重写的。
另外阿里巴巴代码规范中:
这里的重写我们可以理解为按照规范重写。不是随便乱写。太主观了就失去意义了。
建议
如果重写equals和hashCode
,必须定义一致。如果a.equalse(b)返回true,那么a.hashCode()和b.hashCode()必须有相关的值。
equals和hashcode的关系
equals 不相等,
hashCode
可能相等(hash碰撞)。equals 相等,请重写
hashCode
方法,保证hashCode
相等。
关于equals和==,请看另外一篇文章田哥:面试被问== 与equals 的区别,该怎么回答?。