分享

吊打面试官系列:说说hashCode和equals方法

 田维常 2020-11-25


首先我们需要知道hashCode方法和equals方法都是属于Object类的方法。既然属于Object中public修饰的方法,那言外之就是所有对象默认都有这两个方法,只是有时候有的对象已对这两个方法进行了重写。

hashCode方法是返回一个对象的hash值(int类型),利用对象地址生成一个int类型的数。

equals方法是比较两个对象是否为同一个对象。

两者关系

从本质上来讲,两者是完全能没有什么关系的。但是在某些使用场景下,两者关系非常不一般。

什么场景呢?

比如说作为HashMapHashtable等散列表的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 的区别,该怎么回答?

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多