前言

上一篇文章 如何妙用 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;

	@Override
public 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,初识容量,为什么要这样写?你能解释出原因吗?

欢迎持续关注公众号:「日拱一兵」

  • 前沿 Java 技术干货分享
  • 高效工具汇总 | 回复「工具」
  • 面试问题分析与解答
  • 技术资料领取 | 回复「资料」

以读侦探小说思维轻松趣味学习 Java 技术栈相关知识,本着将复杂问题简单化,抽象问题具体化和图形化原则逐步分解技术问题,技术持续更新,请持续关注......


Java equals 和 hashCode 的这几个问题可以说明白吗?的更多相关文章

  1. 总结Java equals()和hashCode()的联系

    Java equals()和hashCode()的联系 如果两个对象是相等的,那么它们必须有相同的哈希码. 如果两个对象具有相同的哈希码,他们可能相等,可能不相等. 如果两个对象是不同的类的实例 他们 ...

  2. Java equals 和 hashcode 方法

    问题 面试时经常会问起字符串比较相关的问题, 总结一下,大体是如下几个: 1.字符串比较时用的什么方法,内部实现如何? 2.hashcode的作用,以及重写equal方法,为什么要重写hashcode ...

  3. java :equals()和hashcode()方法的结合使用

    哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: 1 public native int hashCode(); 根据 ...

  4. Java == ,equals 和 hashcode 的区别和联系(阿里面试)

    今天阿里的人问我 equals 与hashcode的区别,我答不上来, 仔细查了一下,做了总结: (1) == 这是Java 比较内存地址,就是内存中的对象: java中的==是比较两个对象在JVM中 ...

  5. Java equals() 和hashCode()方法详解

    Java的Object类中定义了equals方法,Object类中的equals方法源代码如下,从源代码中可以看出Object类中的equals方法是用来返回判断两个对象是否指向同一个对象(引用地址) ...

  6. java equals 与 hashCode

    转:http://m.blog.csdn.net/blog/pengchua/2297547# 如果你为某个类写了equals方法,那么应该同时编写hashCode方法.如果没有提供hashcode方 ...

  7. java equals 和hashcode

    1  如果不知道怎么重载hashcode, eclipse自动代码生成工具会帮助你生成,大概的思路是设定一个int prim,  然后根据各个成员的值或者hashcode值进行某种运算即可,具体什么运 ...

  8. java equals和hashcode方法

    equals()方法比较两个对象的引用是否相同 hashcode()方法比较两个对象的哈希码是否相同

  9. Java基础拾遗(二) — 关于equals(),hashcode()和 ==

    这里分别讲==和equals()的关系,以及equals()和hashcode()的关系 讲解之前,需要先明白对象的内容.对象的引用,基本类型,引用类型这几个概念,此处不做解释 一.==和equals ...

随机推荐

  1. 解决移动端浏览器 HTML 音频不能自动播放的三种方法

    https://blog.csdn.net/PY0312/article/details/90349386 由于Android,IOS移动端的浏览器以及微信自带的浏览器为了用户更好的体验,规定不自动播 ...

  2. ubuntu中安装Python3.7

    一. 源码安装: 1. 官网源码下载: Python官网:https://www.python.org/downloads/ setuptools官网:https://pypi.org/project ...

  3. mybatis 插入数据返回 -1

    通常使用mybatis对数据进行增删改会进行返回值的判断, 返回值不为1时说明该条语句执行失败,不过今天遇到程序报错返回值不为1,去数据库查看却发现插入成功了,后来知道原来是因为mybatis一次对多 ...

  4. Elasticsearch入门教程(三):Elasticsearch索引&映射

    原文:Elasticsearch入门教程(三):Elasticsearch索引&映射 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文 ...

  5. RabbitMQ入门教程(十七):消息队列的应用场景和常见的消息队列之间的比较

    原文:RabbitMQ入门教程(十七):消息队列的应用场景和常见的消息队列之间的比较 分享一个朋友的人工智能教程.比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看. 这是网上的一篇教程写的很好,不知原作 ...

  6. .Net Core 认证系统源码解析

    不知不觉.Net Core已经推出到3.1了,大多数以.Net为技术栈的公司也开始逐步的切换到了Core,从业也快3年多了,一直坚持着.不管环境怎么变,坚持自己的当初的选择,坚持信仰 .Net Cor ...

  7. 运维ipvsadm配置负载均衡2

    一.什么是lvs1.lvs的定义LVS是Linux Virtual Server的简写,意即Linux虚拟服务器,是一个虚拟的服务器集群系统.是由章文嵩博士开发的一款开源软件,1998年5月发布,是中 ...

  8. php-fpm内存泄漏问题排查

    生产环境内存泄漏问题排查,以下是排查思路   生产环境上有严重的内存溢出问题(红色框所示,正常值应为是 20M 左右)同时系统有 Core Dump 文件产生排查过程中还发现一个现象,如果关闭 OPc ...

  9. docker常用技巧

    1:运行中容器如何保存为一个镜像? docker commit 容器名字 镜像名字 2:怎么给容器增加名字 docker rename 容器id(或名字)name(新名字) 3:docker中的Doc ...

  10. 286-基于6U VPX 的mSATA高性能数据存储板

    基于6U VPX 的mSATA高性能数据存储板 一.板卡概述 该产品系我司自主研发.基于标准6U VPX架构. 二.产品特性       最大存储容量8TB        读写方式RAID0 ,读写速 ...