前言

上一篇文章 如何妙用 Spring 数据绑定?灵魂追问 环节留下了一个有关 equals 和 hashcode 问题 。基础面试经常会碰到与之相关的问题,这不是一个复杂的问题,但很多朋友都苦于说明他们二者的关系和约束,于是写本文做单独说明,本篇文章将循序渐进 ( 通过举例,让记忆与理解更轻松 ) 说明这些让你有些苦恼的问题,Let's go .......

面试问题

1. Java 里面有了 == 运算符,为什么还需要 equals ?

== 比较的是对象地址,equals 比较的是对象值

先来看一看 Object 类中 equals 方法:

public boolean equals(Object obj) {return (this == obj);
}

我们看到 equals 方法同样是通过 == 比较对象地址,并没有帮我们比较值。Java 世界中 Object 绝对是"老祖宗" 的存在,== 号我们没办法改变或重写。但 equals 是方法,这就给了我们重写 equals 方法的可能,让我们实现其对值的比较:

@Override
public boolean equals(Object obj) {//重写逻辑
}

新买的电脑,每个电脑都有唯一的序列号,通常情况下,两个一模一样的电脑放在面前,你会说由于序列号不一样,这两个电脑不一样吗?

如果我们要说两个电脑一样,通常是比较其「品牌/尺寸/配置 」(值) ,比如这样:

@Override
public boolean equals(Object obj) {return 品牌相等 && 尺寸相等 && 配置相等
}

当遇到如上场景时,我们就需要重写 equals 方法。这就解释了 Java 世界为什么有了 == 还有equals 这个问题了.

2. equals相等 和 hashcode 相等问题

关于二者,你经常会碰到下面的两个问题:

  • 两个对象 equals 相等,那他们 hashCode 相等吗?
  • 两个对象 hashCode 相等,那他们 equals 相等吗?

为了说明上面两个问题的结论,这里举一个不太恰当的例子,只为方便记忆,我们将 equals 比作一个单词的拼写;hashCode 比作一个单词的发音,在相同语境下:

sea / sea 「大海」,两个单词拼写一样,所以 equals 相等,他们读音 /siː/ 也一样,所以 hashCode 就相等,这就回答了第一个问题:两个对象 equals 相等,那他们 hashCode 一定也相等
sea / see 「大海/看」,两个单词的读音 /siː/ 一样,显然单词是不一样的,这就回答了第二个问题:两个对象 hashCode 相等,那他们 equals 不一定相等

查看 Object 类的 hashCode 方法:

public native int hashCode();

继续查看该方法的注释,明确写明关于该方法的约束

其实在这个结果的背后,还有的是关于重写 equals 方法的约束

3. 重写 equals 有哪些约束?

关于重写 equals 方法的约束,同样在该方法的注释中写的很清楚了,我在这里再说明一下:

赤橙红绿青蓝紫,七彩以色列;哆来咪发唆拉西, 一曲安哥拉 ,这些规则不是用来背诵的,只是在你需要重写 equals 方法时,打开 JDK 查看该方法,按照准则重写就好

4. 什么时候需要我们重写 hashCode

为了比较值,我们重写 equals 方法,那什么时候又需要重写 hashCode 方法呢?

通常只要我们重写 equals 方法就要重写 hashCode 方法

为什么会有这样的约束呢?按照上面讲的原则,两个对象 equals 相等,那他们的 hashCode 一定也相等。如果我们只重写 equals 方法而不重写 hashCode 方法,看看会发生什么,举个例子来看:

定义学生类,并通过 IDE 只帮我们生成 equals 方法:

public class Student {private String name;private int age;@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;return age == student.age &&Objects.equals(name, student.name);}
}

编写测试代码:

Student student1 = new Student();
student1.setName("日拱一兵");
student1.setAge(18);Student student2 = new Student();
student2.setName("日拱一兵");
student2.setAge(18);System.out.println("student1.equals(student2)的结果是:" + student1.equals(student2));Set<Student> students = new HashSet<Student>();
students.add(student1);
students.add(student2);
System.out.println("Student Set 集合长度是:" + students.size());Map<Student, java.lang.String> map = new HashMap<Student, java.lang.String>();
map.put(student1, "student1");
map.put(student2, "student2");
System.out.println("Student Map 集合长度是:" + map.keySet().size());

查看运行结果:

student1.equals(student2)的结果是:true
Student Set 集合长度是:2
Student Map 集合长度是:2

很显然,按照集合 Set 和 Map 加入元素的标准来看,student1 和 student2 是两个对象,因为在调用他们的 put (Set add 方法的背后也是 HashMap 的 put)方法时, 会先判断 hash 值是否相等,这个小伙伴们打开 JDK 自行查看吧

所以我们继续重写 Student 类的 hashCode 方法:

@Override
public int hashCode() {return Objects.hash(name, age);
}

重新运行上面的测试,查看结果:

student1.equals(student2)的结果是:true
Student Set 集合长度是:1
Student Map 集合长度是:1

得到我们预期的结果,这也就是为什么通常我们重写 equals 方法为什么最好也重写 hashCode 方法的原因

  • 如果你在使用 Lombok,不知道你是否注意到 Lombok 只有一个 @EqualsAndHashCode 注解,而没有拆分成 @Equals 和 @HashCode 两个注解,想了解更多 Lombok 的内容,也可以查看我之前写的文章 Lomok 使用详解
  • 另外通过 IDE 快捷键生成重写方法时,你也会看到这两个方法放在一起,而不是像 getter 和 setter 那样分开

以上两点都是隐形的规范约束,希望大家也严格遵守这个规范,以防带来不必要的麻烦,记忆的方式有多样,如果记不住这个文字约束,脑海中记住上面的图你也就懂了

5. 重写 hashCode 为什么总有 31 这个数字?

细心的朋友可能注意到,我上面重写 hashCode的方法很简答, 就是用了 Objects.hash 方法,进去查看里面的方法:

public static int hashCode(Object a[]) {if (a == null)return 0;int result = 1;for (Object element : a)result = 31 * result + (element == null ? 0 : element.hashCode());return result;
}

这里通过 31 来计算对象 hash 值

如何妙用 Spring 数据绑定? 文章末尾提到的在 HandlerMethodArgumentResolverComposite 类中有这样一个成员变量:

private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(256);

Map 的 key 是 MethodParameter ,根据我们上面的分析,这个类一定也会重写 equalshashCode 方法,进去查看发现,hashCode 的计算也用到了 31 这个数字

@Override
public boolean equals(Object other) {if (this == other) {return true;}if (!(other instanceof MethodParameter)) {return false;}MethodParameter otherParam = (MethodParameter) other;return (this.parameterIndex == otherParam.parameterIndex && getMember().equals(otherParam.getMember()));
}@Override
public int hashCode() {return (getMember().hashCode() * 31 + this.parameterIndex);
}

为什么计算 hash 值要用到 31 这个数字呢?我在网上看到一篇不错的文章,分享给大家,作为科普,可以简单查看一下: String hashCode 方法为什么选择数字31作为乘子

总结

如果还对equalshashCode 关系及约束含混,我们只需要按照上述步骤逐步回忆即可,更好的是直接查看 JDK 源码;另外拿出实际的例子来反推验证是非常好的办法。如果你还有相关疑问,也可以留言探讨.

灵魂追问

  1. Thread 类就没有重写 equals 方法,你还知道哪些情况没必要重写 equals 方法吗?
  2. 从上面 HandlerMethodArgumentResolverComposite 类中定义的 Map 成员变量,你注意到哪些知识点,比如 final,ConcurrentHashMap,初识容量,为什么要这样写?你能解释出原因吗?

hashcode 和 equals 的关系_Java equals 和 hashCode 的这几个问题可以说明白吗?相关推荐

  1. java equals返回值_Java equals() 方法

    equals() 方法用于判断 Number 对象与方法的参数进是否相等. 语法 public boolean equals(Object o) 参数 o -- 任何对象. 返回值 如 Number ...

  2. java重写面试题_Java面试题:重写了equals方法,为什么还要重写hashCode方法?

    核心问题:重写了equals方法,为什么还要重写hashCode方法? 这不仅仅是一道面试题,而且是关系到我们的代码是否健壮和正确的问题.在前面两篇文章涉及到了equals方法的底层讲解:<说说 ...

  3. java 中hashcode 与 equals的关系

    先来看能表明hashcode和equals的关系的几句话: equals()相等的两个对象,hashcode()一定相等: equals()不相等的两个对象,却并不能证明他们的hashcode()不相 ...

  4. a.hashCode() 有什么用?与 a.equals(b)有什么关系?

    //hashCode方法部分源码 public native int hashCode();//equals方法部分源码 public boolean equals(Object obj) {retu ...

  5. 深入探究Java中hashCode()和equals()的关系

    目录 一.基础:hashCode() 和 equals() 简介 equals() hashCode() 二. 漫谈:初识 hashCode() 与 equals() 之间的关系 三. 解密:深入理解 ...

  6. java equals重写原则_java中为何重写equals时必须重写hashCode方法详解

    前言 大家都知道,equals和hashcode是java.lang.Object类的两个重要的方法,在实际应用中常常需要重写这两个方法,但至于为什么重写这两个方法很多人都搞不明白. 在上一篇博文Ja ...

  7. 重写 equals 方法就一定要重写 hashCode 方法?其实有个前提

    作者 l 会点代码的大叔(CodeDaShu) 如果问到 == 和 equals 的区别,相信很多程序员同学都能脱口而出:一个是判断地址,一个是判断内容. 但是如果继续追问:"你重写过 eq ...

  8. 重写equals方法的hashcode_Java equals 和 hashCode 的这几个问题可以说明白吗?

    前言 上一篇文章 如何妙用Spring 数据绑定机制,灵魂追问 环节留下了一个有关 equals 和 hashcode 问题 .基础面试经常会碰到与之相关的问题,这不是一个复杂的问题,但很多朋友都苦于 ...

  9. 面试题:重写equals方法为什么通常会重写hashcode方法?

    最近在面试的时候,当问完了HashMap的数据结构之后,通常会再多问一个问题,就是:重写equals方法时通常为什么也要重写一下hashcode方法? 其实这个问题,本质上又回到HashMap的应用场 ...

最新文章

  1. python学习第二课要点记录
  2. JavaScript实现http地址自动检测并添加URL链接
  3. 宜人贷蜂巢API网关技术解密之Netty使用实践
  4. 获取进程CPU占用率
  5. 第3章 Python 数字图像处理(DIP) - 灰度变换与空间滤波8 - 直方图处理 - 直方图均衡化(全局直方图均衡化)
  6. 2019 .NET China Conf之我逛魔都
  7. WebStorm-2019.2.3 下载安装
  8. Magento 创建优惠卷 Create unique coupon code in Magento
  9. MySQL多字节字符集造成主从数据不一致问题
  10. linux 源码包解压编译安装
  11. 面试必掌握的Mysql的11个问题
  12. ea6500 v1 刷梅林_Linksys EA6500v1刷DD-WRT及救砖方法
  13. pandas数据清洗的一些操作
  14. 关于永洪科技官网被再次冒充事件的详细说明
  15. 吴家坟女子专修学院郭杜校区计算机分院的学年总结
  16. [数学/质数筛] 素数筛法
  17. 西瓜卡顿 ANR 优化治理及监控体系建设
  18. react18中使用react-hook-form
  19. OSChina 周六乱弹 ——这么桃色的故事,让张老汉讲述……
  20. 超乎想象!关于5G无人机的最强科普!

热门文章

  1. Python实现网页截图
  2. python之请求报文对比(假定最多二维字典)
  3. spring resttemplate 中文参数_SpringBoot使用RestTemplate访问第三方接口
  4. linux qt绘框,Qt绘制异形窗体
  5. 什么是rip协议其优缺点_南京课工场IT培训:常见动态路由协议之———RIP动态路由实验...
  6. 怎么修改CSDN上传图片水印的字体大小?(去水印)
  7. Java进阶:AtomicReference详解
  8. javascript 给php传值_2020年PHP面试题附答案(实战经验)
  9. maven+springMVC+Eclipse建立工程框架
  10. Zookeeper 服务注册中心