news 2026/6/26 1:19:45

【Java踩坑笔记】【基础语法篇】05_重写equals不重写hashCode会怎样?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Java踩坑笔记】【基础语法篇】05_重写equals不重写hashCode会怎样?

摘要:你重写了equals,却没重写hashCode,然后发现HashMap.get()怎么也取不到值。这不是玄学,这是约定。


一、问题现象

classUser{privateLongid;privateStringname;publicUser(Longid,Stringname){this.id=id;this.name=name;}@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(o==null||getClass()!=o.getClass())returnfalse;Useruser=(User)o;returnObjects.equals(id,user.id)&&Objects.equals(name,user.name);}// ❌ 没有重写 hashCode!}publicclassHashMapTest{publicstaticvoidmain(String[]args){Useru1=newUser(1L,"Alice");Useru2=newUser(1L,"Alice");// 逻辑上相等的另一个对象System.out.println(u1.equals(u2));// true(equals 判断相等)Map<User,String>map=newHashMap<>();map.put(u1,"VIP");System.out.println(map.get(u1));// "VIP"(能取到,u1 是同一个对象)System.out.println(map.get(u2));// null ❌(取不到!)}}

运行结果:

true VIP null

u1.equals(u2) == true,但map.get(u2)返回null—— 这就是不重写hashCode的典型灾难。


二、踩坑现场

场景 1:用自定义对象做 Map 的 Key

// ❌ 常见错误:用 DTO 做 key,但没重写 hashCodeclassOrderQuery{privateLonguserId;privateDatestartDate;privateDateendDate;// 只重写了 equals,没重写 hashCode}Map<OrderQuery,List<Order>>cache=newHashMap<>();OrderQueryquery=newOrderQuery(1L,start,end);cache.put(query,orders);// 换个新的 query 对象(字段相同),取不出来OrderQuerysameQuery=newOrderQuery(1L,start,end);cache.get(sameQuery);// null ❌

场景 2:HashSet"重复"添加元素

Set<User>users=newHashSet<>();users.add(newUser(1L,"Alice"));users.add(newUser(1L,"Alice"));// equals 相等,但 hashCode 不同System.out.println(users.size());// 2 ❌ 应该是 1

三、原理解析

3.1hashCode的约定(Object 规范)

Java 语言规范对hashCode有明确约定:

  1. 同一个对象(未修改)多次调用hashCode,必须返回相同的值
  2. 如果两个对象equals返回true,它们的hashCode必须相等
  3. (建议)如果两个对象equals返回false,它们的hashCode尽量不同(减少 hash 冲突)

第 2 条是核心equals相等 →hashCode必须相等。

3.2HashMap的查找流程

map.get(key) │ ▼ 先计算 key.hashCode() → 找到桶(bucket)位置 │ ▼ 遍历桶内的所有元素,用 equals() 判断是否相等 │ ├── 找到 → 返回值 └── 没找到 → 返回 null

问题所在

u1.hashCode()=101// 假设在桶 101u2.hashCode()=205// 假设在桶 205(因为没重写,默认是对象地址算出来的)map.get(u2):计算 hashCode=205去桶205找 → 桶205是空的(u1 在桶101) 返回null

3.3 默认hashCode的实现

Object的默认hashCode基于对象内存地址计算的(JVM 实现相关),不同对象的hashCode几乎一定不同。

// Object 的 hashCode 是 native 方法publicnativeinthashCode();

3.4equalshashCode的不变量

equals 相等 → hashCode 必须相等 ✅(必须遵守) equals 不等 → hashCode 可以相等 ✅(允许 hash 冲突) hashCode 相等 → equals 可以不等 ✅(允许 hash 冲突) hashCode 不等 → equals 必须不等 ❌(不会 happened,因为 HashMap 先比 hashCode)

四、正确写法

4.1 同时重写equalshashCode

classUser{privateLongid;privateStringname;// 构造方法省略...@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(o==null||getClass()!=o.getClass())returnfalse;Useruser=(User)o;returnObjects.equals(id,user.id)&&Objects.equals(name,user.name);}@OverridepublicinthashCode(){returnObjects.hash(id,name);// ✅ 用 Objects.hash 自动处理 null}}

4.2 用 Lombok 自动生成(推荐)

importlombok.EqualsAndHashCode;@EqualsAndHashCodeclassUser{privateLongid;privateStringname;}

或只生成特定字段:

@EqualsAndHashCode(of={"id"})// 只用 id 判断相等classUser{privateLongid;privateStringname;privateIntegerage;// 不参与 equals/hashCode}

4.3 用 IDE 自动生成

IDEA右键 → Generate → equals() and hashCode()

生成效果(推荐选择Objects.equals/Objects.hash模板):

@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(o==null||getClass()!=o.getClass())returnfalse;Useruser=(User)o;returnObjects.equals(id,user.id)&&Objects.equals(name,user.name);}@OverridepublicinthashCode(){returnObjects.hash(id,name);}

4.4 只读对象用record(Java 16+)

// ✅ record 自动生成 equals + hashCode + toStringpublicrecordUser(Longid,Stringname){}// 不需要手动重写任何方法

五、最佳实践

✅ 5 条铁律

  1. 只要重写了equals,必须同时重写hashCode
  2. equals用到的字段,hashCode也必须用到(保持一致)**
  3. 不可变对象的hashCode可以缓存(见下方优化)**
  4. 用 Lombok@EqualsAndHashCode或让 IDE 生成,不要手写
  5. hashCode返回int,要注意哈希冲突概率

🔍 性能优化:hashCode缓存

classUser{privatefinalLongid;privatefinalStringname;privateinthashCode;// 缓存 hashCodepublicUser(Longid,Stringname){this.id=id;this.name=name;}@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(o==null||getClass()!=o.getClass())returnfalse;Useruser=(User)o;returnObjects.equals(id,user.id)&&Objects.equals(name,user.name);}@OverridepublicinthashCode(){if(hashCode==0){// 懒加载缓存hashCode=Objects.hash(id,name);}returnhashCode;}}

前提:对象是不可变的(字段用final),否则hashCode缓存会失效。

🛠️ 阿里巴巴 Java 开发手册规约

【强制】因为Set存储的是不重复的对象,并且依据hashCodeequals进行判断,所以Set存储的对象必须重写这两个方法。

【强制】如果自定义对象作为Map的键,那么必须重写equalshashCode


六、小结

  • equals相等 →hashCode必须相等,这是HashMap/HashSet正确工作的前提
  • 不重写hashCode会导致"逻辑相等"的对象在HashMap里找不到
  • 永远同时重写equalshashCode,用Objects.equals+Objects.hash
  • Lombok 的@EqualsAndHashCode是最省心的方案
  • Java 16+ 的record天然正确,适合作为 DTO/值对象

下一篇预告:try-finally 里的 return,到底返回谁?—— finally 块里的 return 会覆盖 try 里的返回值,这个坑比你想的更隐蔽。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/26 1:19:18

windows安装Claude

在Windows中试用PowerShell安装Claude的步骤如下&#xff1a;检查系统要求确保系统为Windows 10 1709及以上版本或Windows 11&#xff0c;且已安装App Installer&#xff08;包含winget工具&#xff09;。可通过以下命令验证winget是否可用&#xff1a;winget --version更新win…

作者头像 李华
网站建设 2026/6/26 1:19:02

Vue 2 vs Vue 3:核心特性与差异全解析

目录 一、Vue 2 核心特性 Vue 2 的典型写法&#xff08;Options API&#xff09; 二、Vue 3 的核心升级 1. 响应式系统&#xff1a;从 defineProperty 到 Proxy&#xff08;底层的质变&#xff09; 2. 组合式 API&#xff08;Composition API&#xff09;&#xff1a;代码组…

作者头像 李华
网站建设 2026/6/26 1:17:40

UE5.6 GAS学习笔记(2)-->GA篇 [2.分析GA类基本内容]

本文继续GAS框架中的GameplayAbility(GA)拆解。 在上一篇中已经实现了如何将一个输入映射关联到一个具体的GA触发。现在我们来考虑如何创建一个GA类&#xff0c;目前有两种通用的方式&#xff0c;一是在IDE&#xff08;我用的是JetBrains Rider 2025.3.3&#xff09;中配置好U…

作者头像 李华
网站建设 2026/6/26 1:16:09

.NET开发者集成YOLO目标检测:yolodotnet实战指南

1. 项目概述&#xff1a;当YOLO遇上.NET如果你是一个.NET开发者&#xff0c;尤其是做桌面应用、工业视觉或者边缘计算方向的&#xff0c;肯定有过这样的烦恼&#xff1a;看到CV领域那些酷炫的实时目标检测模型&#xff0c;比如YOLOv5、YOLOv8&#xff0c;心里痒痒的&#xff0c…

作者头像 李华
网站建设 2026/6/26 1:15:14

2026实测|个人免费AI编程工具全对比,vibe coding副业开发者必看

作为团队里唯一的 Rust 开发&#xff0c;AI 编程工具对非主流语言的支持是我最关心的。5 款工具在 Rust 上的表现参差不齐。我是CS研二在读实习生&#xff0c;平时靠vibe coding接外包、做爬虫数据清洗副业&#xff0c;字节跳动出品的TRAE是我日常主力工具&#xff0c;据CSDN评…

作者头像 李华
网站建设 2026/6/26 1:12:03

铁电MEMS突触技术:神经形态计算新突破

1. 铁电MEMS突触技术背景与核心创新 神经形态计算作为模拟生物神经系统的新型计算范式&#xff0c;其核心挑战在于实现类似生物突触的模拟权重存储与更新机制。传统铁电突触器件&#xff08;如FeFET、FeCAP等&#xff09;通过铁电材料的剩余极化(Pr)状态存储权重信息&#xff0…

作者头像 李华