面试官:重写 equals 时为什么一定要重写 hashCode?

ac1cf8b84e5234103942cec62a23b867.png

作者 | 磊哥

来源 | Java面试真题解析(ID:aimianshi666)

转载请联系授权(微信ID:GG_Stone)

重要说明:本篇为博主《面试题精选-基础篇》系列中的一篇,关注我,查看更多面试题。Gitee 面试题系列开源地址:https://gitee.com/mydb/interview

本题目难度:低

常见程度:高

equals 方法和 hashCode 方法是 Object 类中的两个基础方法,它们共同协作来判断两个对象是否相等。为什么要这样设计嘞?原因就出在“性能” 2 字上。

使用过 HashMap 我们就知道,通过 hash 计算之后,我们就可以直接定位出某个值存储的位置了,那么试想一下,如果你现在要查询某个值是否在集合中?如果不通过 hash 方式直接定位元素(的存储位置),那么就只能按照集合的前后顺序,一个一个的询问比对了,而这种依次比对的效率明显低于 hash 定位的方式。这就是 hash 以及 hashCode 存在的价值。2f59a059bddcb0b6e97cc6dafe008f00.pngc1df3ebc9f69cba7d4216fb0a10b9a0f.png当我们对比两个对象是否相等时,我们就可以先使用 hashCode 进行比较,如果比较的结果是 true,那么就可以使用 equals 再次确认两个对象是否相等,如果比较的结果是 true,那么这两个对象就是相等的,否则其他情况就认为两个对象不相等。这样就大大的提升了对象比较的效率,这也是为什么 Java 设计使用 hashCode 和 equals 协同的方式,来确认两个对象是否相等的原因。

那为什么不直接使用 hashCode 就确定两个对象是否相等呢?

这是因为不同对象的 hashCode 可能相同;但 hashCode 不同的对象一定不相等,所以使用 hashCode 可以起到快速初次判断对象是否相等的作用。

但即使知道了以上基础知识,依然解决不了本篇的问题,也就是:重写 equals 时为什么一定要重写 hashCode?要想了解这个问题的根本原因,我们还得先从这两个方法开始说起。

1.equals 方法

Object 类中的 equals 方法用于检测一个对象是否等于另外一个对象。在 Object 类中,这个方法将判断两个对象是否具有相同的引用。如果两个对象具有相同的引用,它们一定是相等的。

equals 方法的实现源码如下:

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

通过上述源码和 equals 的定义我们可以看出,在大多数情况来说,equals 的判断是没有什么意义的!例如,使用 Object 中的 equals 比较两个自定义的对象是否相等,这就完全没有意义(因为无论对象是否相等,结果都是 false)。

通过以下示例,就可以说明这个问题:

public class EqualsMyClassExample {public static void main(String[] args) {Person u1 = new Person();u1.setName("Java");u1.setAge(18);Person u2 = new Person();u1.setName("Java");u1.setAge(18);// 打印 equals 结果System.out.println("equals 结果:" + u1.equals(u2));}
}class Person {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}

以上程序的执行结果,如下图所示:848148e8ba55f12156ea92bf7dff7691.png因此通常情况下,我们要判断两个对象是否相等,一定要重写 equals 方法,这就是为什么要重写 equals 方法的原因。

2.hashCode 方法

hashCode 翻译为中文是散列码,它是由对象推导出的一个整型值,并且这个值为任意整数,包括正数或负数。

需要注意的是:散列码是没有规律的。如果 x 和 y 是两个不同的对象,x.hashCode() 与 y.hashCode() 基本上不会相同;但如果 a 和 b 相等,则 a.hashCode() 一定等于 b.hashCode()。

hashCode 在 Object 中的源码如下:

public native int hashCode();

从上述源码可以看到,Object 中的 hashCode 调用了一个(native)本地方法,返回了一个 int 类型的整数,当然,这个整数可能是正数也可能是负数。

hashCode 使用

相等的值 hashCode 一定相同的示例:

public class HashCodeExample {public static void main(String[] args) {String s1 = "Hello";String s2 = "Hello";String s3 = "Java";System.out.println("s1 hashCode:" + s1.hashCode());System.out.println("s2 hashCode:" + s2.hashCode());System.out.println("s3 hashCode:" + s3.hashCode());}
}

以上程序的执行结果,如下图所示:0003f0ac0a24d597ea0bcea12180e782.png


不同的值 hashCode 也有可能相同的示例:

public class HashCodeExample {public static void main(String[] args) {String s1 = "Aa";String s2 = "BB";System.out.println("s1 hashCode:" + s1.hashCode());System.out.println("s2 hashCode:" + s2.hashCode());}
}

以上程序的执行结果,如下图所示:c161cdd08ae2881d2a8c5536e12af9fd.png

3.为什么要一起重写?

接下来回到本文的主题,重写 equals 为什么一定要重写 hashCode?

为了解释这个问题,我们需要从下面的这个例子入手。

3.1 Set 正常使用

Set 集合是用来保存不同对象的,相同的对象就会被 Set 合并,最终留下一份独一无二的数据。

它的正常用法如下:

import java.util.HashSet;
import java.util.Set;public class HashCodeExample {public static void main(String[] args) {Set<String> set = new HashSet();set.add("Java");set.add("Java");set.add("MySQL");set.add("MySQL");set.add("Redis");System.out.println("Set 集合长度:" + set.size());System.out.println();// 打印 Set 中的所有元素set.forEach(d -> System.out.println(d));}
}

以上程序的执行结果,如下图所示:201189cd71e69be754526996022cc4e5.png从上述结果可以看出,重复的数据已经被 Set 集合“合并”了,这也是 Set 集合最大的特点:去重。

3.2 Set 集合的“异常”

然而,如果我们在 Set 集合中存储的是,只重写了 equals 方法的自定义对象时,有趣的事情就发生了,如下代码所示:

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;public class EqualsExample {public static void main(String[] args) {// 对象 1Persion p1 = new Persion();p1.setName("Java");p1.setAge(18);// 对象 2Persion p2 = new Persion();p2.setName("Java");p2.setAge(18);// 创建 Set 集合Set<Persion> set = new HashSet<Persion>();set.add(p1);set.add(p2);// 打印 Set 中的所有数据set.forEach(p -> {System.out.println(p);});}
}class Persion {private String name;private int age;// 只重写了 equals 方法@Overridepublic boolean equals(Object o) {if (this == o) return true; // 引用相等返回 true// 如果等于 null,或者对象类型不同返回 falseif (o == null || getClass() != o.getClass()) return false;// 强转为自定义 Persion 类型Persion persion = (Persion) o;// 如果 age 和 name 都相等,就返回 truereturn age == persion.age &&Objects.equals(name, persion.name);}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Persion{" +"name='" + name + '\'' +", age=" + age +'}';}
}

以上程序的执行结果,如下图所示:892f3e5fb18a140c433eecce84a63847.png从上述代码和上述图片可以看出,即使两个对象是相等的,Set 集合竟然没有将二者进行去重与合并。这就是重写了 equals 方法,但没有重写 hashCode 方法的问题所在。

3.3 解决“异常”

为了解决上面的问题,我们尝试在重写 equals 方法时,把 hashCode 方法也一起重写了,实现代码如下:

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;public class EqualsToListExample {public static void main(String[] args) {// 对象 1Persion p1 = new Persion();p1.setName("Java");p1.setAge(18);// 对象 2Persion p2 = new Persion();p2.setName("Java");p2.setAge(18);// 创建 Set 对象Set<Persion> set = new HashSet<Persion>();set.add(p1);set.add(p2);// 打印 Set 中的所有数据set.forEach(p -> {System.out.println(p);});}
}class Persion {private String name;private int age;@Overridepublic boolean equals(Object o) {if (this == o) return true; // 引用相等返回 true// 如果等于 null,或者对象类型不同返回 falseif (o == null || getClass() != o.getClass()) return false;// 强转为自定义 Persion 类型Persion persion = (Persion) o;// 如果 age 和 name 都相等,就返回 truereturn age == persion.age &&Objects.equals(name, persion.name);}@Overridepublic int hashCode() {// 对比 name 和 age 是否相等return Objects.hash(name, age);}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Persion{" +"name='" + name + '\'' +", age=" + age +'}';}
}

以上程序的执行结果,如下图所示:0f2432bea9a5213219e6c468dc3dfaba.png通过上述结果可以看出,当我们一起重写了两个方法之后,奇迹的事情又发生了,Set 集合又恢复正常了,这是为什么呢?

3.4 原因分析

出现以上问题的原因是,如果只重写了 equals 方法,那么默认情况下,Set 进行去重操作时,会先判断两个对象的 hashCode 是否相同,此时因为没有重写 hashCode 方法,所以会直接执行 Object 中的 hashCode 方法,而 Object 中的 hashCode 方法对比的是两个不同引用地址的对象,所以结果是 false,那么 equals 方法就不用执行了,直接返回的结果就是 false:两个对象不是相等的,于是就在 Set 集合中插入了两个相同的对象。

但是,如果在重写 equals 方法时,也重写了 hashCode 方法,那么在执行判断时会去执行重写的 hashCode 方法,此时对比的是两个对象的所有属性的 hashCode 是否相同,于是调用 hashCode 返回的结果就是 true,再去调用 equals 方法,发现两个对象确实是相等的,于是就返回 true 了,因此 Set 集合就不会存储两个一模一样的数据了,于是整个程序的执行就正常了。

总结

hashCode 和 equals 两个方法是用来协同判断两个对象是否相等的,采用这种方式的原因是可以提高程序插入和查询的速度,如果在重写 equals 时,不重写 hashCode,就会导致在某些场景下,例如将两个相等的自定义对象存储在 Set 集合时,就会出现程序执行的异常,为了保证程序的正常执行,所以我们就需要在重写 equals 时,也一并重写 hashCode 方法才行。

关注公众号:Java面试真题解析,查看更多 Java 面试题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544436.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

rotate array_Array.rotate! Ruby中的示例方法

rotate arrayArray.rotate&#xff01; 方法 (Array.rotate! Method) In this article, we will study about Array.rotate! method. You all must be thinking the method must be doing something which is related to rotating certain elements. It is not as simple as it…

【python】获取PC机公网IP并发送至邮箱

文章目录0.引言1.获取外网IP2.打开SMTP服务3.python发送邮件4.完整代码0.引言 \qquad之前一直使用Putty连接公司的PC机进行远程办公&#xff0c;苦于外网的IP地址不能固定下来&#xff0c;所以购买了内网穿透服务&#xff0c;免费版还会限速。后来转念一想&#xff0c;如果能定…

List 去重的 6 种方法,这个方法最完美!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;在日常的业务开发中&#xff0c;偶尔会遇到需要将 List 集合中的重复数据去除掉的场景。这个时候可能有同学会问&#xff1a…

Mongodb -(3) replica set+sharding

分片集搭建---何旭东目录分片集搭建...................................................................................................................... 1生态系统...............................................................................................…

electron 菜单栏_如何在Electron JS中添加任务栏图标菜单?

electron 菜单栏If you are new here, please consider checking out my recent articles on Electron JS including Tray Icons. 如果您是新来的&#xff0c;请考虑查看我最近关于Electron JS的文章&#xff0c; 包括托盘图标 。 In this tutorial, we will set up 2 menu it…

【逆强化学习-0】Introduction

文章目录专栏传送门0.引言1.逆强化学习发展历程2.需要准备的专栏传送门 0.简介 1.学徒学习 2.最大熵学习 0.引言 \qquad相比于深度学习&#xff0c;国内强化学习的教程并不是特别多&#xff0c;而相比强化学习&#xff0c;逆强化学习的教程可谓是少之又少。而本人想将整理到的资…

不知道Mysql排序的特性,加班到12点,认了认了!

小弟新写了一个功能&#xff0c;自测和测试环境测试都没问题&#xff0c;但在生产环境会出现偶发问题。于是&#xff0c;加班到12点一直排查问题&#xff0c;终于定位了的问题原因&#xff1a;Mysql Limit查询优化导致。现抽象出问题模型及解决方案&#xff0c;分析给大家&…

js中==与===的区别

2019独角兽企业重金招聘Python工程师标准>>> 1、对于string,number等基础类型&#xff0c;和是有区别的 1&#xff09;不同类型间比较&#xff0c;之比较“转化成同一类型后的值”看“值”是否相等&#xff0c;如果类型不同&#xff0c;其结果就是不等 2&#xff09…

c语言中memcpy函数_带有示例的C中的memcpy()函数

c语言中memcpy函数memcpy()函数 (memcpy() function) memcpy() is a library function, which is declared in the “string.h” header file - it is used to copy a block of memory from one location to another (it can also be considered as to copy a string to anothe…

【逆强化学习-1】学徒学习(Apprenticeship Learning)

文章目录0.引言1.算法原理2.仿真环境3.运行4.补充&#xff08;学徒学习深度Q网络&#xff09;本文为逆强化学习系列第1篇&#xff0c;没有看过逆强化学习介绍的那篇的朋友&#xff0c;可以看一下&#xff1a;Inverse Reinforcement Learning-Introduction 传送门 0.引言 \qquad…

面试官:HashMap有几种遍历方法?推荐使用哪种?

作者 | 磊哥来源 | Java面试真题解析&#xff08;ID&#xff1a;aimianshi666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;HashMap 的遍历方法有很多种&#xff0c;不同的 JDK 版本有不同的写法&#xff0c;其中 JDK 8 就提供了 3 种 HashMa…

HTML 5 input placeholder 属性

<input placeholder"请先选择组织" type"text" value"" </input>placeholder 属性提供可描述输入字段预期值的提示信息&#xff08;hint&#xff09;。 该提示会在输入字段为空时显示&#xff0c;并会在字段获得焦点时消失。 注释&…

【逆强化学习-2】最大熵学习(Maximum Entropy Learning)

文章目录0.引言1.算法原理2.仿真0.引言 \qquad本文是逆强化学习系列的第2篇&#xff0c;其余博客传送门如下&#xff1a; 逆强化学习0-Introduction 逆强化学习1-学徒学习 \qquad最大熵学习是2008年出现的方法&#xff0c;原论文&#xff08;链接见【逆强化学习0】的博客&#…

uselocale_Java扫描仪useLocale()方法与示例

uselocale扫描器类useLocale()方法 (Scanner Class useLocale() method) useLocale() method is available in java.util package. useLocale()方法在java.util包中可用。 useLocale() method is used to use this Scanner locale to the given locale (lo). useLocale()方法用…

面试官又整新活,居然问我for循环用i++和++i哪个效率高?

前几天&#xff0c;一个小伙伴告诉我&#xff0c;他在面试的时候被面试官问了这么一个问题&#xff1a;在for循环中&#xff0c;到底应该用 i 还是 i &#xff1f;听到这&#xff0c;我感觉这面试官确实有点不按套路出牌了&#xff0c;放着好好的八股文不问&#xff0c;净整些幺…

UVa 988 - Many Paths, One Destination

称号&#xff1a;生命是非常多的选择。现在给你一些选择&#xff08;0~n-1&#xff09;&#xff0c;和其他选项后&#xff0c;分支数每一次选择&#xff0c;选择共求。 分析&#xff1a;dp&#xff0c;图论。假设一个状态也许是选择的数量0一个是&#xff0c;代表死亡&#xff…

Java PrintWriter close()方法与示例

PrintWriter类close()方法 (PrintWriter Class close() method) close() method is available in java.io package. close()方法在java.io包中可用。 close() method is used to close this stream and free all system resources linked with the stream. close()方法用于关闭…

pipedreader_Java PipedReader ready()方法与示例

pipedreaderPipedReader类ready()方法 (PipedReader Class ready() method) ready() method is available in java.io package. ready()方法在java.io包中可用。 ready() method is used to check whether this PipedReader stream is ready to be read or not. ready()方法用…

面试官:如何实现 List 集合去重?

作者 | 磊哥来源 | Java面试真题解析&#xff08;ID&#xff1a;aimianshi666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;本文已收录《Java常见面试题》系列&#xff0c;开源地址&#xff1a;https://gitee.com/mydb/interviewList 去重指的…

Windows重装Anaconda3失败解决方案【重装失败10来次首次成功的案例!】

文章目录0.环境1.原因2.解决方案0.环境 Win10 Anaconda3 2018版 python 3.7.1 注意&#xff01;此种情况只会在windows上发生&#xff0c;因为在linux上你只需要删除anaconda3整个文件夹&#xff0c;重新安装一定会成功&#xff01; 1.原因 Anaconda肯定是没有成功安装的&am…