为什么重写equals方法时必须重写hashcode方法?

由于需要比较对象内容,所以我们通常会重写 equals 方法,但是重写 equals 方法的同时也需要重写 hashCode 方法,有没有想过为什么?

因为如果不这样做的话,就会违反 hashCode 的通用约定,从而导致该类无法结合所有基于散列的集合一起正常工作,这类集合包括 HashMap 和 HashSet。

这里的通用约定,从 Object 类的 hashCode 方法的注释可以了解,主要包括以下几个方面,

  • 在应用程序的执行期间,只要对象的 equals 方法的比较操作所用到的信息没有被修改,那么对同一个对象的多次调用,hashCode 方法都必须始终返回同一个值。
  • 如果两个对象根据 equals 方法比较是相等的,那么调用这两个对象中的 hashCode 方法都必须产生同样的整数结果。
  • 如果两个对象根据 equals 方法比较是不相等的,那么调用者两个对象中的 hashCode 方法,则不一定要求 hashCode 方法必须产生不同的结果。但是给不相等的对象产生不同的整数散列值,是有可能提高散列表(hash table)的性能。

从理论上来说如果重写了 equals 方法而没有重写 hashCode 方法则违背了上述约定的第二条,相等的对象必须拥有相等的散列值。

但是规则是大家默契的约定,如果我们就喜欢不走寻常路,在重写了 equals 方法后没有覆盖 hashCode 方法,会产生什么后果吗?

我们自定义一个 Student 类,并且重写了 equals 方法,但是我们没有重写 hashCode 方法,那么当调用 Student 类的 hashCode 方法的时候,默认就是调用超类 Object 的 hashCode 方法,根据随机数返回的一个整型值。

public class Student {
  private String name;
  private String gender;
  public Student(String name, String gender) {
    this.name = name;
    this.gender = gender;
  }
  @Override
  public boolean equals(Object anObject) {
    if (this == anObject) {
      return true;
    }
    if (anObject instanceof Student) {
      Student anotherStudent = (Student) anObject;
      if (this.getName() == anotherStudent.getName()
          || this.getGender() == anotherStudent.getGender())
        return true;
    }
    return false;
  }
}

我们创建两个对象并且设置属性值一样,测试下结果:

public static void main(String[] args) {
  Student student1 = new Student("小明", "male");
  Student student2 = new Student("小明", "male");
  System.out.println("equals结果:" + student1.equals(student2));
  System.out.println("对象1的散列值:" + student1.hashCode() + ",对象2的散列值:" + student2.hashCode());
}

得到的结果

equals结果:true
对象1的散列值:1058025095,对象2的散列值:665576141

我们重写了 equals 方法,根据姓名和性别的属性来判断对象的内容是否相等,但是 hashCode 由于是调用 Object 类的 hashCode 方法,所以打印的是两个不相等的整型值。

如果这个对象我们用 HashMap 存储,将对象作为 key,熟知 HashMap 原理的同学应该知道,HashMap 是由数组 + 链表的结构组成,这样的结果就是因为它们 hashCode 不相等,所以放在了数组的不同下标,当我们根据 Key 去查询的时候结果就为 null。

public static void main(String[] args) {
  Student student1 = new Student("小明", "male");
  Student student2 = new Student("小明", "male");
  HashMap<Student, String> hashMap = new HashMap<>();
  hashMap.put(student1, "小明");
  String value = hashMap.get(student2);
  System.out.println(value);
}

输出结果

null

得到的结果我们肯定不满意,这里的 student1 和 student2 虽然内存地址不同,但是它们的逻辑内容相同,我们认为它们应该是相同的。

这里如果不好理解,可以将 Student 类换成 String 类思考下,String 类是我们常常作为 HashMap 的 Key 值使用的,试想如果 String 类只重写了 equals 方法而没有重写 HashCode 方法,这里将某个字符串 new String("s") 作为 Key 然后 put 一个值,但是再根据 new String("s") 去 Get 的时候却得到 null 的结果,这是难以让人接受的。

所以无论是理论的约定上还是实际编程中,我们重写 equals 方法的同时总要重写 hashCode 方法,请记住这点。

虽然 hashCode 方法被重写了,但是如果我们想要获取原始的 Object 类中的哈希码,我们可以通过 System.identityHashCode(Object a)来获取,该方法返回默认的 Object 的 hashCode 方法值,即使对象的 hashCode 方法被重写了也不影响。

public static native int identityHashCode(Object x);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值