一、equals方法和hashcode的关系

根据Object.hashCode的通用约定:

  1. 如果两个对象相同(equals方法返回true),那么hashcode也相等。(图1)
  2. 如果两个对象的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方法的更多相关文章

  1. java -为什么重写equals(),还需要重写hashCode()?

    1.先post这两个方法的基本定义: equals()的定义: 浅谈Java中的equals和==(转) hashCode()的定义: java中hashCode()方法的作用 Java中hashCo ...

  2. 第九条:覆盖equals方法时总要覆盖hashCode方法

    Object类的hashCode方法: public native int hashCode();   是一个本地方法. 其中这个方法的主要注释如下: Whenever it is invoked o ...

  3. why在重写equals时还必须重写hashcode方法

    首先我们先来看下String类的源码:可以发现String是重写了Object类的equals方法的,并且也重写了hashcode方法 public boolean equals(Object anO ...

  4. 覆写equals方法为什么需要覆写hashCode方法

    覆写equals方法必须覆写hashCode方法,是JDK API上反复说明的,不过为什么要这样做呢?这两个方法之间有什么关系呢? void test() { // Person类的实例作为Map的k ...

  5. 【Java实战】源码解析为什么覆盖equals方法时总要覆盖hashCode方法

    1.背景知识 本文代码基于jdk1.8分析,<Java编程思想>中有如下描述: 另外再看下Object.java对hashCode()方法的说明: /** * Returns a hash ...

  6. java重写equals方法

    @Override public int hashCode() { return task.getId(); } @Override public boolean equals(Object obj) ...

  7. java重写equals方法需要注意的几点

    为什么equals()方法要重写? 判断两个对象在逻辑上是否相等,如根据类的成员变量来判断两个类的实例是否相等,而继承Object中的equals方法只能判断两个引用变量是否是同一个对象.这样我们往往 ...

  8. Java重写equals方法(重点讲解)

    为什么equals()方法要重写? 判断两个对象在逻辑上是否相等,如根据类的成员变量来判断两个类的实例是否相等,而继承Object中的equals方法只能判断两个引用变量是否是同一个对象.这样我们往往 ...

  9. 重写equals()与hashCode()方法

    出自:http://blog.csdn.net/renfufei/article/details/16339351 Java语言是完全面向对象的,在java中,所有的对象都是继承于Object类.Oj ...

  10. Java中equals和==的区别?为什么重写equals方法后,一定要重写hashCode方法?

    首先明确一点,equals是方法,==是操作符. 1. 如果比较的是基本数据类型: 只讨论==,因为equals是不存在的,因为java中基本数据类型不能调用method的. 2. 如果比较的是引用类 ...

随机推荐

  1. 编辑crontab添加Linux计划任务

    在做实验楼的比赛时的题目 用到了crontable 1. 为用户shiyanlou添加一个计划任务 2. 每天凌晨2点定时执行 3. 将/var/log/dpkg.log /var/log/mysql ...

  2. coroutine - 示例

    分享流畅的python一书, coroutine 章节中的出租车仿真的例子. from collections import namedtuple import queue import random ...

  3. finished with exit code -1073740791 (0xC0000409)解决方案

    1.在用keras框架跑NER的train时,而且只是在用了keras_contrib.layers的CRF时出现问题: 遇到无错跳出finished with exit code -10737407 ...

  4. 一. 数据分片和路由 <<大数据日知录>> 读书笔记

    本章主要讲解大数据下如何做数据分片,所谓分片,即将大量数据分散在不同的节点,同时每个存储节点还要做副本备份. 而一般的抽象分片方法是, 先将数据映射到一个分片空间,这是多对一的关系,即一个数据分片区间 ...

  5. GPU 版 TensorFlow failed to create cublas handle: CUBLAS_STATUS_ALLOC_FAILED

    原因: 使用 GPU 版 TensorFlow ,并且在显卡高占用率的情况下(比如玩游戏)训练模型,要注意在初始化 Session 的时候为其分配固定数量的显存,否则可能会在开始训练的时候直接报错退出 ...

  6. PgSQL备份

    SQL转储. 这里我们用到的工具是pg_dump和pg_dumpall. 这种方式可以在数据库正在使用的时候进行完整一致的备份,并不阻塞其它用户对数据库的访问.它会产生一个脚本文件,里面包含备份开始时 ...

  7. Spring Cloud(八):使用Spring Cloud Bus来实现配置动态更新

    使用Spring Cloud Config我们能实现服务配置的集中化管理,在服务启动时从Config Server获取需要的配置属性.但如果在服务运行过程中,我们需要将某个配置属性进行修改,比如将验证 ...

  8. 【5min+】后台任务的积木。.NetCore中的IHostedService

    系列介绍 [五分钟的dotnet]是一个利用您的碎片化时间来学习和丰富.net知识的博文系列.它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net ...

  9. 痞子衡嵌入式:恩智浦i.MX RT1xxx系列MCU启动那些事(11.1)- FlexSPI NOR连接方式大全(RT1015/1020/1050)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MX RT1015/1020/1050三款MCU的FlexSPI NOR启动的连接方式. 由于i.MXRT内部没有非易失性存储 ...

  10. StackExchange.Redis 之 List队列 类型示例

    //从第1个开始,依次向左插入值.如果键不存在,先创建再插入值 队列形式 先进后出,后进先出 //插入后形式  <-- 10,9,8,7,6,5,4,3,2,1 <-- 方向向左依次进行 ...