为什么重写equals一定要重写hashCode?

本文最后更新于:2023年3月23日 上午

规范

在每个类中,在重写equals方法的时侯,一定要重写hashCode方法。

如果不这样做,你的类违反了hashCode的通用约定,对于HashSet, HashMap, HashTable等基于hash值的类就会出现问题。

Object规范

根据Object规范,规范约定:

  • 如果两个对象通过equals方法比较是相等的,那么它们的hashCode方法结果值也是相等的。
  • 如果两个对象通过equals方法比较是不相等的,那么不要求它们的hashCode方法结果值是相等的。
  • 当在一个应用程序执行过程中, 如果equals方法比较中没有修改任何信息,那么在同一个对象上重复调用hashCode方法时,它必须始终返回相同的值。但如果从一个应用程序到了另一个应用程序,两个应用程序汇中调用hashCode方法的返回值可以是不一致的。

Object类中的默认的equals和hashCode方法:

  • equals:比较的是对象的内存地址是否相同(相当于==操作符);
  • hashCode:hashCode方法的返回值符合上述规范。

hashCode规范

  • 两个对象相等,hashcode一定相等
  • 两个对象不等,hashcode不一定不等
  • hashcode相等,两个对象不一定相等
  • hashcode不等,两个对象一定不等

图示:

对象与哈希值对应关系
equals方法与hashCode方法根本就是配套使用的。对于任何一个对象,不论是使用继承自Object的equals方法还是重写equals方法。hashCode方法实际上必须要完成的一件事情就是,为该equals方法认定为相同的对象返回相同的哈希值。

举例

String类的equals方法经过重写,具体实现源码如下:String类equals方法的重写实现
通过源码我们能看到,String对象在调用equals方法比较另一个对象时,除了认定相同地址值的两个对象相等以外,还认定对应着的每个字符都相等的两个String对象也相等(即使这两个String对象实际上不是同一个对象,他们的地址值不同)。对,String类中对equals方法进行重写扩充了。

如果此时我们不将hashCode方法也进行重写,那么String类调用的就是来自顶级父类Obejct类中的hashCode方法。即,对于两个字符串对象,使用他们各自的地址值映射为哈希值。

导致的结果就是:被String类中的equals方法认定为相等的两个对象拥有两个不同的哈希值(因为他们的地址值不同)。

结论

问:为什么重写equals一定要重写hashcode?

答:因为必须保证重写后的equals方法认定相同的两个对象拥有相同的哈希值。

同时我们顺便得出了一个结论:“hashCode方法的重写原则就是保证equals方法认定为相同的两个对象拥有相同的哈希值”。

拓展

前面有提到:

如果不这样做(指重写equals一定要重写hashCode),你的类违反了hashCode的通用约定,对于HashSet, HashMap, HashTable等基于hash值的类就会出现问题。

以HashMap为例,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。
这样解决了向含有大量数据的集合中添加元素时,大量频繁的操作equals方法的问题。

也正是因此,重写equals一定要重写hashCode,避免存储数据的时候出现违反规范(对于HashMap来说就是存储重复数据)的情况。

详细具体测试可以看以下两个链接:


为什么重写equals一定要重写hashCode?
https://moechun.fun/2022/10/16/为什么重写equals一定要重写hashCode?/
作者
Knight Kilito
发布于
2022年10月16日
更新于
2023年3月23日
许可协议