当前位置: 首页 > news >正文

hashCode 与 equals:面试官必问的核心关联,一篇讲透

文章目录

  • 前言
  • 一、先搞懂:hashCode 到底有什么用?
    • 1. 哈希码的本质:一个 “身份标识” 的简化版
    • 2. 核心应用场景:哈希集合的高效操作
    • 3. 关于 hashCode 的两个重要约定
  • 二、再理清:equals 方法的本职工作
    • 1. Object 类的默认实现
    • 2. 重写 equals 的正确姿势
  • 三、关键核心:为什么重写 equals 必须重写 hashCode?
    • 1. 反例:只重写 equals,不重写 hashCode
    • 2. 反例分析:违背了 hashCode 的等价性约定
    • 3. 正确做法:重写 equals 时,必须重写 hashCode
  • 四、总结:hashCode 与 equals 的核心关联
  • 五、面试高频坑点提醒

前言

大家好,我是程序员梁白开,今天我们聊一聊hashCode 与 equals。

在 Java 面试中,hashCodeequals绝对是高频考点,很多同学都能说出 “重写 equals 必须重写 hashCode”,但问到两者到底有什么用、为什么要强制绑定,却常常含糊其辞。今天就带大家从底层原理到实际应用,彻底搞懂这两个方法的 “爱恨情仇”。


一、先搞懂:hashCode 到底有什么用?

hashCode() 是 Java 中 Object 类的一个原生方法,它的核心作用是返回对象的哈希码值(int 类型),这个哈希码主要用于快速查找,是 HashMap、HashSet 等哈希集合的 “性能基石”。

1. 哈希码的本质:一个 “身份标识” 的简化版

你可以把哈希码理解为对象的一个 “简化指纹”。理论上,每个对象都有自己的内存地址(独一无二),但直接用内存地址作为标识进行查找,效率并不高。

hashCode() 会通过特定算法,将对象的内存地址或内部属性转化为一个 int 整数,这个整数就是哈希码,它的核心价值在于缩小查找范围

2. 核心应用场景:哈希集合的高效操作

我们以HashMap为例,看看 hashCode 是如何发挥作用的:

  1. 当向 HashMap 中 put 元素 时,会先计算 key 的 hashCode,根据 hashCode 直接定位到对应的哈希桶(数组下标)。
  2. 如果该哈希桶为空,直接将键值对存入;如果不为空,再通过 equals() 方法比较桶内元素与新 key 是否相等:
    • 相等则覆盖旧值;
    • 不相等则以链表或红黑树的形式挂载(解决哈希冲突)。
  3. 当从 HashMap 中 get 元素 时,同样先计算 key 的 hashCode,快速定位到哈希桶,再通过 equals() 精准匹配目标元素。

试想一下,如果没有 hashCode,每次查找都要遍历 HashMap 中的所有元素,时间复杂度会从 O (1) 退化到 O (n),在数据量大的场景下,性能差距会极其明显。

3. 关于 hashCode 的两个重要约定

根据 Java 官方文档,hashCode() 需要遵循以下通用约定,这是我们重写方法的准则:

  1. 一致性:在同一个 Java 程序执行期间,对同一个对象多次调用 hashCode(),必须返回相同的整数,前提是对象用于 equals() 比较的属性没有被修改。
  2. 等价性:如果两个对象通过 equals() 方法比较为相等,那么它们的 hashCode() 必须返回相同的整数。
  3. 非唯一性:如果两个对象通过 equals() 方法比较为不相等,它们的 hashCode() 可以相同(这就是哈希冲突),但建议不同,以提高哈希集合的性能。

二、再理清:equals 方法的本职工作

equals() 同样是 Object 类的原生方法,它的核心作用是 判断两个对象是否 “逻辑相等”。

1. Object 类的默认实现

Object 类中 equals() 的源码如下:

publicbooleanequals(Objectobj){return(this==obj);}

可以看到,默认的 equals() 本质上是 比较两个对象的内存地址,也就是判断两个引用是否指向同一个对象。

但在实际开发中,我们往往需要的是 “逻辑相等”。比如,对于一个 User 类,只要 id 相同,我们就认为两个 User 对象是相等的,这时候就需要 重写 equals() 方法。

2. 重写 equals 的正确姿势

以 User 类为例,重写 equals() 的规范写法:

publicclassUser{privateLongid;privateStringname;// 构造方法、getter/setter 省略@Overridepublicbooleanequals(Objecto){// 1. 自反性:自己和自己比较,返回 trueif(this==o)returntrue;// 2. 非空性 + 类型判断:避免空指针,且确保是同一类if(o==null||getClass()!=o.getClass())returnfalse;// 3. 类型强转,比较核心属性Useruser=(User)o;returnObjects.equals(id,user.id);// 用 Objects.equals 避免空指针}}

重写 equals() 时,要遵循 自反性、对称性、传递性、一致性 这四个原则,这里不再展开,感兴趣的同学可以查阅官方文档。

三、关键核心:为什么重写 equals 必须重写 hashCode?

这是面试的核心问题,我们用 反例 来理解这个强制要求的必要性。

1. 反例:只重写 equals,不重写 hashCode

假设我们只重写了 User 类的 equals() 方法(按 id 比较),但没有重写 hashCode(),此时 Object 类的默认 hashCode() 会根据对象内存地址生成哈希码。

publicstaticvoidmain(String[]args){Useru1=newUser(1L,"张三");Useru2=newUser(1L,"李四");// 因为 id 相同,equals 返回 trueSystem.out.println(u1.equals(u2));// true// 但默认 hashCode 基于内存地址,u1 和 u2 是不同对象,哈希码不同System.out.println(u1.hashCode());// 比如:123456System.out.println(u2.hashCode());// 比如:789012// 放入 HashSet 中HashSet<User>set=newHashSet<>();set.add(u1);set.add(u2);// 预期:因为 u1 和 u2 相等,set 中应该只有一个元素// 实际:set 中存在两个元素!System.out.println(set.size());// 输出 2}

2. 反例分析:违背了 hashCode 的等价性约定

上面的代码中,u1 和 u2 通过 equals() 比较为相等,但它们的 hashCode() 却不相同,这就 违背了 hashCode 的第二个约定。

当把这两个对象放入 HashSet 时:

  • 存入 u1:计算 u1 的 hashCode,定位到哈希桶 A,存入。
  • 存入 u2:计算 u2 的 hashCode,定位到哈希桶 B,存入。
  • 由于两个对象在不同的哈希桶中,HashSet 不会再调用 equals() 进行比较,最终导致两个 “相等” 的对象被同时存入集合,破坏了 HashSet 的 “元素唯一性” 特性。

3. 正确做法:重写 equals 时,必须重写 hashCode

我们为 User 类补充 hashCode() 的重写,保证相等的对象具有相同的哈希码:

@OverridepublicinthashCode(){// 基于 equals 中比较的核心属性 id 生成哈希码returnObjects.hash(id);}

此时再运行上面的测试代码:

  • u1.equals(u2) 为 true,u1.hashCode() 和 u2.hashCode() 也相同。
  • 存入 HashSet 时,u2 会定位到和 u1 相同的哈希桶,通过 equals() 比较后发现相等,不会被重复存入。
  • 最终 set.size() 输出 1,符合预期。

四、总结:hashCode 与 equals 的核心关联

维度hashCodeequals
核心作用生成对象哈希码,用于快速查找判断两个对象逻辑相等
调用时机哈希集合(HashMap/HashSet)添加、查询元素时优先调用哈希集合中定位到同一哈希桶后,用于精准匹配
关联规则相等的对象,hashCode 必须相同相同 hashCode 的对象,equals 不一定相等

一句话总结:
hashCode 是 “粗筛”,帮我们快速缩小查找范围;equals 是 “细筛”,帮我们精准判断对象是否相等。两者协同工作,才能保证哈希集合的高效与正确性。

五、面试高频坑点提醒

  1. 不要用随机数生成 hashCode:违反一致性约定,同一对象多次调用 hashCode 会返回不同值。
  2. 不要只重写 hashCode 而不重写 equals:没有意义,哈希集合依然无法正确判断元素唯一性。
  3. 重写 hashCode 时,要基于 equals 中的核心属性:比如 equals 比较 id 和 name,hashCode 也要包含这两个属性。

http://www.cnnetsun.cn/news/52261.html

相关文章:

  • 【Java毕设源码分享】基于springboot+vue的高校本科生学习成长记录系统的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 2003-2024年上市公司高管政治关联、政企纽带数据
  • 2025年更新!人工智能企业数据库
  • 全面沦陷:所有 LLM 与 AI 绘画模型已被攻破——红队实战全景报告(2025)
  • systemd服务管理深入实践从入门到自定义服务
  • 基于微信小程序的网络安全知识科普平台系统【源码文末联系】
  • 基于VUE的实验室使用管理系统[VUE]-计算机毕业设计源码+LW文档
  • 【单片机毕业设计】【mcugc-mcu911】基于单片机的多功能安防系统
  • 【单片机毕业设计】【mcugc-mcu916】基于单片机的智能家居安防系统
  • 基于vue的家庭服务预约管理系统的设计与实现_f1709smp_springboot php python nodejs
  • Java---小球移动案例(附代码)
  • 分享一个夸克网盘不限速的下载方法-在线免费工具
  • 1、现代 C++ 挑战:从基础到实战
  • 当数据回归遇上暴击流:SVM Adaboost实战手札
  • 65、文件管理子系统与网络协议通信概述
  • DAY18 机器学习
  • jd.item_review获取京东商品评论 及tb.item_review获取taobao商品评论
  • Windows11系统文件verifier.dll丢失或损坏问题 下载修复
  • C++树形数据结构————树状数组、线段树中“逆序对”的问题
  • 2025年B站视频下载终极指南:bilili工具完整使用教程
  • 教程 32 - 几何体系统
  • Cursor高级技巧与最佳实践
  • Cursor + MCP:冲击的不仅是前端,而是整个软件开发范式!
  • 2025年十大旗舰对决:极致轻薄成高端手机新战场
  • 【Vue3】 中 ref 与 reactive:状态与模型的深入理解
  • 毕设 stm32 RFID员工打卡门禁系统(源码+硬件+论文)
  • 全球最大、最领先的吉利全球全域安全中心正式发布
  • Android中Compose系列之按钮Button
  • wangEditor导入excel数据到html富文本编辑
  • 光伏电池simulink仿真模型 光伏电池建模仿真 包括改变温度 改变辐照度的特性分析 模型可...