查看原文
其他

为什么重写了equals()也要重写hashCode()

MarkerHub 2022-11-21

小Hub领读:

虽然是很基础的一篇文章,但是对于equals、hashcode两个方法,相信很多人都与其中的规则不熟悉,来跟着小Hub花个8分钟回顾一下!


作者:不学无数的程序员

https://my.oschina.net/u/4030990/blog/3134199

在 EffectiveJava中第九条规定在覆盖 equals()方法时总要覆盖 hashCode()方法。这是为什么呢?接下来我们就介绍一下这两个方法。

Java中的 equals()方法和 hashCode()方法都是在 Object类中的方法,而在Java中所有的类都是 Obejct类的子类,所以Java中所有的方法都会有这两个方法的默认实现。

equals方法

Object类中的 equals()方法定义如下

  1. public boolean equals(Object obj) {

  2. return (this == obj);

  3. }

我们发现在 equals()方法中就关键的 ==,那么 ==在Java中有什么含义呢,我们都知道在Java中分为基本数据类型和引用数据类型。那么 ==在这两个类型中作用是不一样的。

  • 基本数据类型:比较的是 ==两边值是否相等

  • 引用数据类型:比较的是 ==两边内存地址是否相等

基本数据类型包括: byteshortcharintlongfloatdoubleboolean

而通过Java文档中的 equals()方法描述,所有要实现自己的 equals()方法都要遵守下面几个规则

  • 自反性:对于任何对象x, x.equals(x)应该返回 true

  • 对称性:对于任何两个对象x和y,如果 x.equals(y)返回 true,那么 y.equals(x)也应该返回 true

  • 传递性:对于多个对象x,y,z,如果 x.equals(y)返回 truey.equals(z)返回 true,那么 y.equals(z)也应该返回 true

  • 一致性:对于两个非空对象x,y,在没有修改此对象的前提下,多次调用返回的结果应该相同

  • 对于任何非空的对象x, x.equals(null)都应该返回 false

hashCode方法

Object中的 hashCode()方法是一个本地方法,返回一个 int类型的哈希值。

  1. public native int hashCode();

在 hashCode()方法中也有一些规约

  • 如果对象在使用 equals方法中进行比较的参数没有修改,那么多次调用一个对象的 hashCode()方法返回的哈希值应该是相同的。

  • 如果两个对象通过 equals方法比较是相等的,那么要求这两个对象的 hashCode方法返回的值也应该是相等的。

  • 如果两个对象通过 equals方法比较是不同的,那么也不要求这两个对象的 hashCode方法返回的值是相同的。但是我们应该知道对于不同对象产生不同的哈希值对于哈希表(HashMap等等)能够提高性能。

equals方法和hashCode方法会在哪用到

这两个方法经常出现在Java中的哪个类里面呢?如果看过 HashMap源码的应该了解这两个方法经常出现在 HashMap中。网上介绍 HashMap类的文章有很多了,这里就简单介绍一下 HashMap

当一个节点中的链表超过了8的时候就会变为红黑树,以解决链表长度过长以后查询速度慢的缺点。

HashMap是由数组和链表组成的高效存储数据的结构。那么是如何确定一个数据存储在数组中的哪个位置呢?就是通过 hashCode方法进行计算出存储在哪个位置,还记得我们上面讲 hashCode方法说了有可能两个不同对象的 hashCode方法返回的值相同,那么此时就会产生冲突,产生冲突的话就会调用 equals方法进行比对,如果不同,那么就将其加入链表尾部,如果相同就替换原数据。

计算位置当然不是上面简单的一个 hashCode方法就计算出来,中间还有一些其他的步骤,这里可以简单的认为是 hashCode确定了位置。

什么时候去覆盖这两个方法呢?

如果你不将自定义的类定义为 HashMap的key值的话,那么我们重写了 equals方法而没有重写 hashCode方法,编译器不会报任何错,在运行时也不会抛任何异常。

如果你想将自定义的类定义为 HashMap的key值得话,那么如果重写了 equals 方法那么就必须也重写 hashCode方法。

接下来我们可以看一下我们使用自定义的类作为 HashMap的key,并且自定义的类不重写 equals和 hashCode方法会发生什么。

自定义的类

  1. @Builder

  2. @NoArgsConstructor

  3. @AllArgsConstructor

  4. class CustomizedKey{

  5. private Integer id;

  6. private String name;

  7. }

接下来我们看使用自定义的类作为key

  1. public static void main(String[] args) {


  2. Map<CustomizedKey, Integer> data = getData();


  3. CustomizedKey key = CustomizedKey.builder().id(1).name("key").build();


  4. Integer integer = data.get(key);


  5. System.out.printf(String.valueOf(integer));

  6. }


  7. private static Map<CustomizedKey,Integer> getData(){

  8. Map<CustomizedKey,Integer> customizedKeyIntegerMap = new HashMap<>();

  9. CustomizedKey key = CustomizedKey.builder().id(1).name("key").build();

  10. customizedKeyIntegerMap.put(key,10);

  11. return customizedKeyIntegerMap;

  12. }

我们可以看到程序最后打印的是一个 null值。原因正如上面我们说的一样。

  • hashCode:用来计算该对象放入数组中的哪个位置,因为是两个都是new的对象,所以即使里面的值一样,但是对象所处的地址却不同,所以使用默认的 hashCode也就不同,当然在 hashMap中就不会认为两个是一个对象。

接下来我们就重写一下这两个方法。如果我们使用 IDEA的话,那么直接使用快捷键即可。

接下来我们看我们实现的两个方法

  1. @Builder

  2. @NoArgsConstructor

  3. @AllArgsConstructor

  4. class CustomizedKey{

  5. private Integer id;

  6. private String name;


  7. @Override

  8. public boolean equals(Object o) {

  9. if (this == o) return true;

  10. if (o == null || getClass() != o.getClass()) return false;

  11. CustomizedKey that = (CustomizedKey) o;

  12. return Objects.equals(id, that.id) &&

  13. Objects.equals(name, that.name);

  14. }


  15. @Override

  16. public int hashCode() {

  17. return Objects.hash(id, name);

  18. }

  19. }

然后我们再次运行上面的程序发现输出打印已经变成了 10

我们也能够使用 Lombock提供的 @EqualsAndHashCode注解简化代码

代码地址:https://github.com/modouxiansheng/Doraemon


(完)

MarkerHub文章索引:(点击阅读原文直达)

https://github.com/MarkerHub/JavaIndex


【推荐阅读】

知乎问答:搞开发就怕加班还学不到东西?

很全很牛逼,看完这篇Elasticsearch实战,我觉得我可以写个百度~

基于Jwt资源无状态认证权限管理系统bootshiro

别用Date了,Java8新特性之日期处理,现在学会也不迟!

eblog项目讲解视频上线啦,长达17个小时!!




好文章!点个在看!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存