重写了 equals,为什么 HashMap 还是找不到你要的对象

发布时间:2026/10/23 9:57:26

重写了 equals,为什么 HashMap 还是找不到你要的对象 「Java 进阶之路」系列 Day20写在前面这是一个几乎每个人都踩过的坑给一个类重写了equals自信满满地觉得两个属性一样的对象现在应该被判定为相等了结果一放进HashSetcontains查一个属性完全一样的新对象返回的却是false。问题不在equals写错了而是漏了另一半——hashCode。这篇就把这两个方法为什么必须绑在一起讲清楚。一、是什么一个判断相等一个决定放哪equals定义的是逻辑相等两个对象即使不是同一个引用只要关心的属性都一样就应该被判定为相等。Object类默认的equals比较的是引用地址等价于——不重写的话两个属性完全一样的不同对象永远不相等。hashCode返回一个int本意是给基于哈希表的容器HashMap、HashSet、Hashtable用来快速定位数据该放进哪个桶bucket。Object默认的hashCode是跟对象内存地址相关的一个整数同一个对象只要没被移动多次调用返回值不变但和equals判定的逻辑相等完全没关系。二、为什么HashMap 靠这两个方法配合才能定位到对象hashCode的官方契约里有一条硬性规定两个对象用equals判定相等hashCode就必须相等反过来不要求——hashCode相同的两个对象完全可以不相等这叫哈希冲突哈希表本来就要处理这种情况。这条契约不是凭空定的而是HashMap.get(key)的查找过程决定的先算key.hashCode()用这个值定位到某个桶再在这个桶里逐个用equals比较找到真正相等的那一个key对象计算hashCode定位到某个桶桶内可能有多个entry逐个用equals比较找到匹配返回没有匹配判定不存在如果只重写了equals、没重写hashCode两个逻辑相等的对象会因为默认的hashCode基于地址不同从一开始就被分到了不同的桶——equals再准确也没用压根不会走到逐个比较这一步。这就是为什么这两个方法必须成对重写equals负责终点的精确比较hashCode负责能不能先走到这个终点。三、怎么用三个容易踩的坑classPoint{intx,y;Point(intx,inty){this.xx;this.yy;}Overridepublicbooleanequals(Objecto){if(thiso)returntrue;if(!(oinstanceofPoint))returnfalse;Pointp(Point)o;returnxp.xyp.y;}// 没有重写 hashCode}SetPointpointsnewHashSet();points.add(newPoint(1,2));System.out.println(points.contains(newPoint(1,2)));// false逻辑相等的对象却查不到正确做法是让hashCode用和equals完全一致的字段来计算OverridepublicinthashCode(){returnObjects.hash(x,y);}Objects.hash内部就是把每个字段的hashCode按31做累乘累加IDE 自动生成、Lombok 的EqualsAndHashCode走的也是同一套思路。一个更隐蔽的坑用可变字段参与hashCode计算。对象放进HashSet之后如果再去修改参与hashCode计算的字段这个对象的hashCode会变但它在哈希表里的桶位置是插入那一刻就定死的、不会跟着重新调整——于是这个对象实际上迷失了后续用contains/remove都定位不到它原来所在的桶看起来就像对象凭空消失。参与equals/hashCode计算的字段最好是不可变的或者整个对象干脆设计成不可变对象。另一个容易忽略的坑类型检查该用instanceof还是getClass()。上面Point的equals用的是instanceof如果Point被继承出一个ColorPoint多了一个颜色字段instanceof会认为父类的Point(1, 2)和子类的ColorPoint(1, 2, 红色)只要坐标一样就相等——但反过来ColorPoint的equals通常会比较颜色判定两者不相等。这样就出现了p.equals(cp)为true、cp.equals(p)为false破坏了equals契约里的对称性a.equals(b)和b.equals(a)结果必须一致。改用o.getClass() ! this.getClass()判断类型能规避这个问题要求类型完全一致父子类之间永远不相等代价是丧失了父类引用可以和子类对象比较的灵活性——这也是为什么《Effective Java》建议优先用组合代替继承来复用带equals的类从设计上绕开这个两难。四、面试追问Q1为什么重写 equals 一定要重写 hashCode因为HashMap/HashSet这类基于哈希表的容器查找时先用hashCode()定位桶再在桶内用equals()精确比较。只重写equals不重写hashCode两个equals判定相等的对象会因为默认hashCode基于地址不同而被分到不同桶导致contains/get永远找不到本该相等的元素违反了equals相等则hashCode必须相等这条硬性契约。Q2hashCode 相等的两个对象equals 一定相等吗不一定。hashCode允许出现哈希冲突equals不相等的对象完全可以有相同的hashCode反过来equals相等则hashCode必须相等这是单向约束不能反过来推。Q3用可变字段参与 hashCode 计算会有什么风险对象放入哈希表之后如果修改了参与hashCode计算的字段对象的hashCode会变但它在哈希表里的桶位置是插入时就定死的不会跟着重新调整于是这个对象实际上丢失了——后续用get/contains/remove都找不到它。所以参与计算的字段最好是不可变的。Q4Object 类默认的 equals 和 hashCode 分别是什么行为默认equals比较的是引用地址等价于默认hashCode返回一个跟对象内存地址相关的整数同一个对象多次调用结果不变但和逻辑相等没有关系。不重写的话两个属性完全一样的不同对象永远被判定为不相等。Q5用 Lombok 的EqualsAndHashCode或 IDE 自动生成时要注意什么要确认参与计算的字段集合和业务定义的相等语义一致——比如继承关系下父类字段有没有正确纳入集合类字段参与计算时要保证不可变、不会在放入哈希表之后被修改如果手动排除了部分字段要保证equals和hashCode排除的是同一批字段不能两边字段集合对不上否则照样违反契约。Q6equals 里用 instanceof 判断类型可能有什么问题如果这个类被继承子类通常会在equals里多比较自己新增的字段。这样一来父类对象和子类对象比较时父类的equals用instanceof可能判定两者相等但子类的equals会因为多出来的字段判定不相等导致a.equals(b)和b.equals(a)结果不一致破坏了equals契约里的对称性。改用getClass()做严格的类型比较能规避这个问题代价是父类和子类对象之间永远不可能相等。下一篇预告模块二到这里就收尾了Day21 开始进入模块三集合框架ArrayList和LinkedList到底该怎么选从源码层面把两者的性能差异掰开揉碎讲清楚。
延伸阅读

更多相关文章

2026/10/23 9:57:23

吃透标准 IO:缓冲区、读写流程避坑完整总结

标准IO总结 标准IO是标准C库提供的库函数&#xff0c;头文件<stdio.h>&#xff0c;操作对象为文件流指针FILE*。底层会调用文件IO系统调用&#xff0c;自带用户层缓冲区&#xff0c;可移植性强。 一、系统预定义3个文件流流指针含义对应硬件文件IO等价fdstdin标准输入流键…

2026/10/22 10:38:22

又一个Mac截图神器,仅6.9M、原生免费还没订阅

大家都知道&#xff0c;我是一名程序员&#xff0c;用Mac电脑比较平凡&#xff08;一般公司都是配mac&#xff09;。 平时没啥爱好&#xff0c;就是分享一些AI技术、开源工具等等&#xff0c;除了写代码、写博客&#xff0c;一天下来截屏次数其实也不再少数。 以前我固定走两…

2026/10/18 18:27:09

血细胞检测数据集本质与使用边界解析

简介&#xff1a;本资源为面向计算机视觉与医学图像分析方向研究者及深度学习初学者的血细胞检测专用数据集&#xff0c;旨在支撑红细胞、白细胞、血小板等多类血细胞的识别、分割与分类模型训练与评估。数据集结构规范&#xff0c;共1753个文件&#xff0c;含874张高质量JPG显…

2026/10/23 9:54:51

FreeRDP命令行实战:Windows远程桌面批量连接与参数配置指南

上周有个做运维的朋友甩过来一句抱怨&#xff1a;手头二十多台 Windows 机器的远程会话&#xff0c;每天要一台台点开系统自带的远程桌面连接、填地址、填账号、点确定&#xff0c;一天下来手指头都酸了&#xff0c;更别提还要在几个会话之间来回切着看日志。他问我有没有办法把…

2026/10/23 9:54:51

Linux安装微信的真相:四大路径实测与决策指南

1. 为什么“在Linux系统中安装微信”是个高频却总被误答的问题 “如何在Linux系统中安装微信”——这个搜索词每天在各大技术社区、问答平台和新手群组里出现数百次。它看起来简单&#xff0c;但背后藏着一个典型的认知断层&#xff1a;多数提问者真正想问的&#xff0c;不是“…

2026/10/23 9:54:51

CubeStudio多集群演进:从单机到多资源组的GPU算力平台实践

最早跑CubeStudio单机版的时候&#xff0c;一台双卡机器加一块共享存储就能把模型开发、训推的流程全部兜住。但等团队多了、项目组多了、GPU卡不够分的时候&#xff0c;你很快会发现单机这摊事根本撑不住。要么排队排到天荒地老&#xff0c;要么不同项目的环境互相干扰&#x…

2026/10/23 9:54:51

MATLAB实战:MFCC特征提取全链路拆解与避坑指南

简介&#xff1a;这份MATLAB代码资源聚焦梅尔倒谱系数&#xff08;MFCC&#xff09;的完整实现&#xff0c;面向语音识别、情感分析与语音合成方向的学习者和开发者&#xff0c;尤其适合希望从零理解特征提取流程的初学者&#xff0c;也可作为专业人士的可靠工具。代码按预加重…

2026/10/23 9:54:50

力扣高效刷题指南:从新手到算法高手

1. 为什么选择力扣作为算法练习平台作为一名从传统CRUD业务转型到算法领域的开发者&#xff0c;我最初接触力扣&#xff08;LeetCode&#xff09;时也经历过一段迷茫期。经过5年持续刷题和3次大厂面试实战后&#xff0c;我深刻理解到这个平台的设计价值。力扣的题目库覆盖了算法…

2026/10/23 9:49:48

.Net Core 2.1 JWT身份认证实战:从Token生成到问题排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/22 11:29:28

金融AI落地指南:从调研报告到技术选型与工程实践

简介&#xff1a;这份PDF报告聚焦2025年金融服务业中国AI现状与趋势&#xff0c;面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者&#xff0c;帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件&#xff0c;大小约1…

2026/10/23 0:02:26

Verilog实现FPGA密码锁:矩阵键盘与状态机设计实战

说实话&#xff0c;这几年接触了不少想入门FPGA的朋友&#xff0c;如果你问哪个项目能一口气把Verilog的核心基本功串起来&#xff0c;我通常会推荐电子密码锁。这个项目看着不起眼&#xff0c;却把矩阵键盘扫描、按键消抖、状态机设计、动态显示刷新、外设控制这些数字逻辑里的…

2026/10/23 0:02:26

数据服务监控运维实战:从指标体系到告警治理与故障排查

周末凌晨两点&#xff0c;手机震了。第一反应是“数据服务挂了”。打开电脑看了下&#xff0c;果然&#xff0c;某张核心报表的数据产出时间已经超过SLA两小时&#xff0c;下游三个业务方连着发来问询。这种场景&#xff0c;做过大数据运维的人都不会陌生。数据服务的监控与运维…

2026/10/23 0:02:26

FreeRTOS任务通知实战详解:内核原理、API与避坑指南

裸机编程切到RTOS之后&#xff0c;任务之间的通信和同步永远是躲不开的坎。前面聊过队列、信号量、互斥锁、事件组&#xff0c;这次把FreeRTOS任务通知&#xff08;Task Notification&#xff09;单独拎出来讲透。原因很简单&#xff1a;任务通知是FreeRTOS里最容易用错、也最容…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑