为什么重写equals方法,还必须要重写hashcode方法
一、equals方法和hashcode的关系
根据Object.hashCode的通用约定:
- 如果两个对象相同(equals方法返回true),那么hashcode也相等。(图1)
- 如果两个对象的hashcode相等,这两个对象不一定相同,因为可能发生了hash冲突。(图2)
啥是hash冲突呢?
举个例子:下图的hash算法为 hashcode = num % 4;

图1
看上图,如果有两个对象都是13(意思也就是指向了同一个对象,那它们的hashcode当然也是相等的啦),所以hashcode的值都是1。(13 % 4 = 1)

图2
上图的两个对象的hashcode的值都是1,但是一个对象是13,一个对象是17,它们发生了hash冲突。
二、什么情况下,需要重写equals方法和hashcode方法?
以HashSet为例,它的元素是无序且不可重复的。所以在添加一个元素前,就需要判断集合里是不是已经有了这个元素。
那么该怎么判断呢?
如果用equals方法,那就得遍历整个集合去看一一比较有没有重复,想添加一个元素就需要O(n)的时间复杂度。
如果用hashcode方法,我只需计算出hash值,然后根据hash值去查找是否已经有这个元素了,如果hash算法选的合适,只需要O(1)的时间复杂度(如果hash算法选的不好,会造成多次hash冲突,极端情况下会变成O(n)。此处默认hash算法选的合适,毕竟不是本文要探讨的问题)。
所以通常是先执行hashcode方法,如果一样,再执行equals方法。
在Object类里,equals方法和hashcode方法都是比较对象的内存地址。但是在HashSet里,我们不想根据内存地址去判断是否相等,想根据key值去判断是否相等,所以我们需要重写。


一定要同时重写这两个方法吗?
是的,否则这两个方法默认比较内存地址。
我们可以设计一个实验,看看是否必须同时重写这两个方法。
实验数据:
现在我们有两个人:
Person p1 = new Person("张三");
Person p2 = new Person("张三");
实验前提:
1.先执行hashcode方法,如果结果一样,再执行equals方法。
2.这两个方法如果不重写,默认比较内存地址。
实验步骤与预测实验结果:
实验1步骤:两个方法都不重写。
预测实验结果:集合里会有两个张三。(因为两个对象的内存不一样)
实验2步骤:重写equals方法,不重写hashcode方法。
预测实验结果:集合里会有两个张三。(先执行hashcode方法,但这两个张三的内存地址不一样,hashcode也不一样,所以会有两个张三,不执行equals方法,在hash表的分布如下图)

实验3步骤:重写hashcode方法,不重写equals方法。
预测实验结果:集合里会有两个张三。(先重写并执行hashcode方法,发现两个都是张三,hashcode一样,接着执行equals方法,发现内存地址不一样,所以会有两个张三,在hash表的分布如下图)
下图这里出现了hash冲突。

实验4步骤:重写两个方法。
预测实验结果:集合里只有1个张三,达到了HashSet不重复的目的。(先重写并执行hashcode方法,发现两个都是张三,hashcode一样,接着重写并执行equals方法,发现两个都是张三,返回true,所以程序确认两个张三其实是同一个人)
下面做的实验结果和这里的推论一样,不想看可以直接跳到结尾看总结。
创建一个Person类:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
//按照实验要求选择性注释
/*@Override
public boolean equals(Object obj) {
if(obj == null) {
return false;
}
if(obj.getClass() != obj.getClass()){
return false;
}
return ((Person)obj).getName() == getName();
}*/
//按照实验要求选择性注释
/*@Override
public int hashCode() {
return getName().hashCode();
}*/
}
再来个测试类:
import java.lang.*;
import java.util.HashSet;
import java.util.Set; public class Main { public static void main(String[] args) {
Set<Person> set = new HashSet<>();
Person p1 = new Person("张三");
Person p2 = new Person("张三");
set.add(p1);
set.add(p2);
System.out.println(set);
}
}
实验1:两个方法都不重写。
结果:

实验2:重写equals方法,不重写hashcode方法。
结果:

实验3:重写hashcode方法,不重写equals方法。
结果:

实验4:重写两个方法。
结果:

三、总结
Q1:什么场景下,需要重写equals方法和hashcode方法?
A1:判断两个对象是否相等,不是根据对象的内存地址去判断,而是根据key去判断的时候,就需要重写。
比如HashSet集合。
Q2:为什么重写equals方法,还必须要重写hashcode方法?
A2:
1.先执行hashcode方法,如果结果一样,再执行equals方法。
2.这两个方法如果不重写,默认比较内存地址。
如果只重写其中一个,那么另一个还是会比较内存地址。这样则违背了“不是根据对象的内存地址去判断,而是根据key去判断的时候”的重写前提。
为什么重写equals方法,还必须要重写hashcode方法的更多相关文章
- java -为什么重写equals(),还需要重写hashCode()?
1.先post这两个方法的基本定义: equals()的定义: 浅谈Java中的equals和==(转) hashCode()的定义: java中hashCode()方法的作用 Java中hashCo ...
- 第九条:覆盖equals方法时总要覆盖hashCode方法
Object类的hashCode方法: public native int hashCode(); 是一个本地方法. 其中这个方法的主要注释如下: Whenever it is invoked o ...
- why在重写equals时还必须重写hashcode方法
首先我们先来看下String类的源码:可以发现String是重写了Object类的equals方法的,并且也重写了hashcode方法 public boolean equals(Object anO ...
- 覆写equals方法为什么需要覆写hashCode方法
覆写equals方法必须覆写hashCode方法,是JDK API上反复说明的,不过为什么要这样做呢?这两个方法之间有什么关系呢? void test() { // Person类的实例作为Map的k ...
- 【Java实战】源码解析为什么覆盖equals方法时总要覆盖hashCode方法
1.背景知识 本文代码基于jdk1.8分析,<Java编程思想>中有如下描述: 另外再看下Object.java对hashCode()方法的说明: /** * Returns a hash ...
- java重写equals方法
@Override public int hashCode() { return task.getId(); } @Override public boolean equals(Object obj) ...
- java重写equals方法需要注意的几点
为什么equals()方法要重写? 判断两个对象在逻辑上是否相等,如根据类的成员变量来判断两个类的实例是否相等,而继承Object中的equals方法只能判断两个引用变量是否是同一个对象.这样我们往往 ...
- Java重写equals方法(重点讲解)
为什么equals()方法要重写? 判断两个对象在逻辑上是否相等,如根据类的成员变量来判断两个类的实例是否相等,而继承Object中的equals方法只能判断两个引用变量是否是同一个对象.这样我们往往 ...
- 重写equals()与hashCode()方法
出自:http://blog.csdn.net/renfufei/article/details/16339351 Java语言是完全面向对象的,在java中,所有的对象都是继承于Object类.Oj ...
- Java中equals和==的区别?为什么重写equals方法后,一定要重写hashCode方法?
首先明确一点,equals是方法,==是操作符. 1. 如果比较的是基本数据类型: 只讨论==,因为equals是不存在的,因为java中基本数据类型不能调用method的. 2. 如果比较的是引用类 ...
随机推荐
- 在centos6.3下安装php的Xdebug
首先下载一个xdebug http://www.xdebug.org/docs/ 官网上有windos版本和linux源码版本的,我们下载一个源码包xdebug-2.2.5.tgz 然后进入安装流程 ...
- Asp.Net Api+Swagger控制器注释
Swagger注释不显示,只需要进入Startup.cs 找到: c.IncludeXmlComments(Path.Combine(AppDomain.CurrentDomain.BaseDirec ...
- 论文《Entity Linking with Effective Acronym Expansion, Instance Selection and Topic Modeling》
Entity Linking with Effective Acronym Expansion, Instance Selection and Topic Modeling 一.主要贡献 1. pro ...
- [Memcached]操作
telnet连接memcached 查看端口是否可访问 # telnet 172.16.1.1 11211 Trying 172.16.1.1... Connected to 172.16.1.1. ...
- asp.net core 3.x 授权默认流程
一.前言 接上一篇<asp.net core 3.x 授权中的概念>,本篇看看asp.net core默认授权的流程.从两个方面来看整个授权系统是怎么运行的:启动阶段的配置.请求阶段中间件 ...
- 【二】、UML基础知识——图图解乾坤
[二].UML基础知识 UML概述 UML是一个通用的可视化建模语言,不同于编程语言,它通过一些标准的图形符号和文字来对系统进行建模.用于对软件进行描述.可视化处理.构建软件系统的文档.是一套总结了以 ...
- 单源最短路 Bellman-Ford算法(有向图)
// 单源最短路问题 // Bellman-Ford算法 // 复杂度O(V*E) //! 可以判断负圈 #include <cstdio> #include <iostream&g ...
- Github上优秀的.NET Core项目
Github上优秀的.NET Core开源项目的集合.内容包括:库.工具.框架.模板引擎.身份认证.数据库.ORM框架.图片处理.文本处理.机器学习.日志.代码分析.教程等. Github地址:htt ...
- python3-cookbook笔记:第七章 函数
python3-cookbook中每个小节以问题.解决方案和讨论三个部分探讨了Python3在某类问题中的最优解决方式,或者说是探讨Python3本身的数据结构.函数.类等特性在某类问题上如何更好地使 ...
- nodejs爬虫--抓取CSDN某用户全部文章
最近正在学习node.js,就像搞一些东西来玩玩,于是这个简单的爬虫就诞生了. 准备工作 node.js爬虫肯定要先安装node.js环境 创建一个文件夹 在该文件夹打开命令行,执行npm init初 ...