前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >当HashMap的键遇见自定义类型时

当HashMap的键遇见自定义类型时

作者头像
JavaEdge
发布于 2022-11-29 00:49:05
发布于 2022-11-29 00:49:05
41000
代码可运行
举报
文章被收录于专栏:JavaEdgeJavaEdge
运行总次数:0
代码可运行

1 概述

这是Java中经典的问题,在面试中也经常被问起.很多书提到要重载hashCode()和equals()两个方法才能实现自定义键在HashMap中的查找,但是为什么要这样以及如果不这样做会产生什么后果,好像很少有文章讲到,所以来这一篇记录下.

2 案例分析

首先,如果我们直接用以下的Person类作为键,存入HashMap中,会发生发生什么呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.csdn;

/**
 * @author Shusheng Shi
 * @since 2017/6/4 11:49
 */
public class Person {

    private String id;

    public Person(String id) {
        this.id = id;
    }
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.csdn;

import java.util.HashMap;

/**
 * @author Shusheng Shi
 * @since 2017/6/4 16:03
 */

public class Main {
    public static void main(String[] args) {

        HashMap<Person, String> map = new HashMap<>();

        map.put(new Person("001"), "findingsea");
        map.put(new Person("002"), "linyin");
        map.put(new Person("003"), "henrylin");
        map.put(new Person("003"), "findingsealy");

        System.out.println(map.toString());

        System.out.println(map.get(new Person("001")));
        System.out.println(map.get(new Person("002")));
        System.out.println(map.get(new Person("003")));
    }
}

那么输出结果是什么呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{com.csdn.Person@74a14482=henrylin, com.csdn.Person@4554617c=linyin, com.csdn.Person@1b6d3586=findingsea, com.csdn.Person@1540e19d=findingsealy}
null
null
null

我们可以看到,这里出现了两个问题:

  1. 在添加的过程中,将key=new Person(“003”)的键值对添加了两次,那么在期望中,HashMap中应该只存在一对这样的键值对,因为key(期望的)是相同的,所以不应该重复添加,第二次添加的value=”findingsealy”应该替换掉原先的value=”henrylin”.但是在输入中,我们发现期望中的情况并没有出现,而是在HashMap同时存在了value=”findingsealy”和value=”henrylin”的两个键值对,并且它们的key值还是不相同的,这显然是错误的;
  2. 在获取value值时,我们分别用三个Person对象去查找,这三个对象和我们刚刚存入的三个key值(在期望中)是相同的,但是查找出的却是三个null值,这显然也是错误的.

那么,正确的方法是直接对Person类进行修改,重写equals和hashCode方法,修改过后的Person类如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.csdn;

/**
 * @author Shusheng Shi
 * @since 2017/6/4 11:49
 */
public class Person {

    private String id;

    public Person(String id) {
        this.id = id;
    }

    @Override
    public boolean equals(Object o) {
        return o instanceof Person && (id == ((Person) o).id);
    }

    @Override
    public int hashCode() {
        return id != null ? id.hashCode() : 0;
    }
}

尽管看起来equals()方法只是检查其参数是否为Person的实例,但是instanceof悄悄地检查了此对象是否为null,因为若instance左边参数为null,它会返回false.若参数不为null,且类型正确,则基于每一个对象中实际的id值的hashCode进行比较.从输出结果也看出,这种方式是正确的.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{com.csdn.Person@ba31=findingsea, com.csdn.Person@ba32=linyin, com.csdn.Person@ba33=findingsealy}
findingsea
linyin
findingsealy

可以看到,之前指出的错误都得到了改正.为什么会这样呢? 在HashMap中,查找key的比较顺序为:

  1. 计算对象的Hash Code,看在表中是否存在;
  2. 检查对应Hash Code位置中的对象和当前对象是否相等.

显然,第一步就是要用到hashCode()方法,而第二步就是要用到equals()方法.在没有进行重载时,这两步会默认调用Object类的这两个方法.

而在Object类中Hash Code默认是使用对象的地址计算的,那两个Person(“003”)的对象地址是不同的,所以它们的Hash Code也不同,自然HashMap也不会把它们当成是同一个key了.同时,在Object默认的equals()中,也是根据对象的地址进行比较,自然一个Person(“003”)和另一个Person(“003”)是不相等的.

理解了这一点,就很容易搞清楚为什么需要同时重载hashCode()和equals两个方法了.

  • 重载hashCode()是为了对同一个key,能得到相同的Hash Code,这样HashMap就可以定位到我们指定的key上.
  • 重载equals()是为了向HashMap表明当前对象和key上所保存的对象是相等的,这样我们才真正地获得了这个key所对应的这个键值对.

还有一个细节,在Person类中对于hashCode()的重在方法为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
public int hashCode() {
    return id != null ? id.hashCode() : 0;
}

这里可能有疑惑的点在于:为什么可以用String类型的变量的Hash Code作为Person类的Hash Code值呢?这样new Person(new String(“003”))和new Person(new String(“003”))的Hash Code是相等的吗?

来看看以下代码的输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
728795174
728795174
728795174
728795174

可以看到四条语句的输出都是相等的,很直观的合理的猜测就是String类型也重载了hashCode()以根据字符串的内容来返回Hash Code值,所以相同内容的字符串具有相同的Hash Code.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

同时,这也说明了一个问题:为什么在已知hashCode()相等的情况下,还需要用equals()进行比较呢?就是因为避免出现上述例子中的出现的情况,因为根据对Person类的hashCode()方法的重载实现,Person类会直接用id这个String类型成员的Hash Code值作为自己的Hash Code值,但是很显然的,一个Person(“003”)和一个String(“003”)是不相等的,所以在hashCode()相等的情况下,还需要用equals()进行比较.

以下例子可以作为上述说明的佐证:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
System.out.println(new Person("003").hashCode()); // 47667
System.out.println(new String("003").hashCode()); // 47667

System.out.println(new Person("003").equals(new String("003"))); // false
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017-06-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java集合类型大揭秘
这里HashMap里面用到链式数据结构的一个概念。上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。打个比方,第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?
leon公众号精选
2022/04/27
2980
Java集合类型大揭秘
在HashMap中将可变对象用作Key,需要注意什么?
本文中我们将会讨论在Java HashMap中将可变对象用作Key。所有的Java程序员可能都在自己的编程经历中多次用过HashMap。那什么是HashMap呢?
九州暮云
2019/08/21
2.7K0
Java HashMap详解及实现原理
Java HashMap是Java集合框架中最常用的实现Map接口的数据结构,它使用哈希表实现,允许null作为键和值,可以存储不同类型的键值对。HashMap提供了高效的存取方法,并且是非线程安全的。在Java中,HashMap被广泛应用于各种场景,如缓存、数据库连接池、路由器等。
程序猿川子
2025/02/17
930
Java HashMap详解及实现原理
JAVA中重写equals()方法为什么要重写hashcode()方法说明
重写hashCode()时最重要的原因就是:无论何时,对同一个对象调用hashCode()都应该生成同样的值。如果在将一个对象用put()方法添加进HashMap时产生一个hashCode()值,而用get()取出时却产生了另外一个 hashCode()值,那么就无法重新取得该对象了。所以,如果你的hashCode()方法依赖于对象中易变的数据,那用户就要小心了,因为此数据发生变化时,hashCode()就会产生一个不同的hash码,相当于产生了一个不同的“键”。        Object的hashCode()方法,返回的是当前对象的内存地址。下次如果我们需要取一个一样的“键”对应的键值对的时候,我们就无法得到一样的hashCode值了。因为我们后来创建的“键”对象已经不是存入HashMap中的那个内存地址的对象了。        我们看一个简单的例子,就能更加清楚的理解上面的意思。假定我们写了一个类:Person (人),我们判断一个对象“人”是否指向同一个人,只要知道这个人的身份证号一直就可以了。        先来个没有重写Code类的hashcode()的例子吧,看看是什么效果:
bear_fish
2018/09/20
1.1K0
JAVA中重写equals()方法为什么要重写hashcode()方法说明
面试中最长常问到的 HashMap,你都知道多少?
推荐阅读:https://zhuanlan.zhihu.com/p/31610616
村雨遥
2020/08/13
3550
HashMap 与 ConcrrentHashMap 使用以及源码原理分析
数组:采用一段连续的存储单元来存储数据。对于指定下标的查找,时间复杂度为O(1);通过给定值进行查找,需要遍历数组,逐一比对给定关键字和数组元素,时间复杂度为O(n),当然,对于有序数组,则可采用二分查找,插值查找,斐波那契查找等方式,可将查找复杂度提高为O(logn);对于一般的插入删除操作,涉及到数组元素的移动,其平均复杂度也为O(n)
小勇DW3
2018/08/30
1.4K0
HashMap 与 ConcrrentHashMap 使用以及源码原理分析
Java中HashMap原理及其使用场景,提供一个自定义HashMap实际案例
Java中的HashMap是一种基于哈希表的数据结构,用于存储键值对。它实现了Map接口,允许我们通过键来快速查找对应的值,具有高效的插入、删除和查找操作。HashMap内部使用数组和链表(或红黑树)组合的方式来实现,它的核心思想是通过哈希算法将键映射到数组索引上,从而实现快速的查找。
用户1289394
2024/05/29
1460
Java中HashMap原理及其使用场景,提供一个自定义HashMap实际案例
了解HashMap
HashMap 主要用来存放键值对,它基于哈希表的 Map 接口实现,是常用的 Java 集合之一,是非线程安全的。
橘子君丶
2023/03/08
4080
了解HashMap
集合(下)
HashSet 是 Set 接口的实现类,底层数据结构是哈希表。HashSet 是线程不安全的(不保证同步)。优点:添加、删除、查询效率高;缺点:无序
Carlos Ouyang
2019/08/19
2880
集合(下)
从底层实现到应用场景:逐层探究HashMap类
今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。
喵手
2023/11/26
4560
从底层实现到应用场景:逐层探究HashMap类
第十九天 集合-Map接口容器工具类集合框架总结【悟空教程】
Map集合的特点,如是否可重复,是否有序仅作用在键上,如HashMap集合的键不得重复,值可以重复。
Java帮帮
2018/07/26
1.2K0
第十九天 集合-Map接口容器工具类集合框架总结【悟空教程】
Carson带你学Java:深入源码解析HashMap 1.8
在了解 如何计算存放数组table 中的位置 后,所谓 知其然 而 需知其所以然,下面我将讲解为什么要这样计算,即主要解答以下3个问题:
Carson.Ho
2022/03/25
4830
Carson带你学Java:深入源码解析HashMap 1.8
HashMap详解
**Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。**这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
程序员阿杜
2023/08/25
2740
JAVA零基础小白学习免费教程day14-Set&HashMap
java.util.Set接口和java.util.List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
用户9184480
2024/12/13
730
自定义对象需要重写hashcode
      Java中的很多对象都override了equals方法,都知道,这是为了能比较两个对象是否相等而定义,如果不需要比较,则不需要定义equals方法。比如StringBuffer类,没有提供equals方法,则说明没有两个StringBuffer对象是相等的。再比如Collections类,全部是静态方法,根本没必要创建对象,所以也就没有提供equals方法。       我们程序中自定义的对象有时候需要比较它们是否相等,也需要重写equals方法。如果我们要将对象放到HashMap或者Hashtable这样的hash集合中的时候,就需要重写hashcode方法了。因为它们是根据hashcode来标识对象的。       如果我们不重写hashcode方法,把他们作为key放入hashmap中是什么情况呢?看看下面代码:
chroya
2018/10/31
1.1K0
Java基础-18(01)总结Map,HashMap,HashMap与Hashtable区别,Collections工具类
(1)将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。 (2)Map和Collection的区别? A:Map 存储的是键值对形式的元素,键唯一,值可以重复。夫妻对
Java帮帮
2018/03/15
7410
Java:手把手带你源码分析 HashMap 1.7
在了解 如何计算存放数组table 中的位置 后,所谓 知其然 而 需知其所以然,下面我将讲解为什么要这样计算,即主要解答以下3个问题: 1. 为什么不直接采用经过hashCode()处理的哈希码 作为 存储数组table的下标位置? 2. 为什么采用 哈希码 与运算(&) (数组长度-1) 计算数组下标? 3. 为什么在计算数组下标前,需对哈希码进行二次处理:扰动处理?
Carson.Ho
2019/02/22
1.4K0
Hashcode的作用_冻干粉的作用与功效
大家好,又见面了,我是你们的朋友全栈君。 前言 博主github 博主个人博客http://blog.healerjean.com 感谢大神HashCode 感谢大神HashMap 1、一些常见的HashCode 1.1、Integer @Test public void Integer_HashCode(){ Integer one = new Integer(20); System.out.println(one.hashCode()
全栈程序员站长
2022/10/05
2K0
Hashcode的作用_冻干粉的作用与功效
Java 8系列之重新认识HashMap
作者:美团点评技术团队 链接:https://zhuanlan.zhihu.com/p/21673805 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
bear_fish
2018/09/19
1.2K0
Java 8系列之重新认识HashMap
这可能是最细的HashMap详解了!
# 手撕HashMap源码 > 文章已同步至GitHub开源项目: [Java超神之路](https://github.com/shaoxiongdu/java-notes) ### HashMap一直是面试的重点。今天我们来了解了解它的源码吧! > 首先看一下Map的继承结构图 ![image-20210906151448379](https://gitee.com/ShaoxiongDu/imageBed/raw/master/image-20210906151448379.png) > 源码
程序员阿杜
2021/09/11
2550
推荐阅读
相关推荐
Java集合类型大揭秘
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文