面试官问:==和equals()有什么区别?为什么重写equals必须重写hashCode?(附图解+避坑指南)
2026/6/16 8:33:07 网站建设 项目流程

面试官问:==和equals()有什么区别?为什么重写equals必须重写hashCode?(附图解+避坑指南)

📝摘要==认地址,equals认内容,hashCode定位置。地址可不同,内容同则位置必同。本文用一图看懂 + 房产证比喻 + HashMap 底层原理 + 现代 Java 最优解(record),带你彻底拿下这道面试必考题。


📚 系列导航

  • 上一篇:面试官问:基本类型和引用类型到底有什么不同?
  • 下一篇预告:面试官问:String、StringBuilder、StringBuffer有什么区别?(待发布)
  • 全部85题目录:点击查看

💬 面试还原

面试官==equals()有什么区别?为什么重写equals()时必须重写hashCode()

这道题从初级到高级都会问,但能答透“为什么必须重写 hashCode”的人不到30%。今天用一张图 + 房产证比喻 + HashMap 底层原理,让你彻底拿下。


🧠 一图看懂(HashMap 存取原理)

金句记忆

==认地址,equals认内容,hashCode定位置。地址可不同,内容同则位置必同。


🍵 生活比喻:房产证 vs 身份证号

  • === 比房产证上的地址是否完全一样(是不是同一套房)。
  • equals()(重写后)= 比两套房子的朝向、面积、装修是否相同(内容相等)。
  • hashCode()= 这套房子所在的小区编号 + 楼栋编号。
    • 同一个小区/楼栋的房产,哈希值(大致)相同。
    • 如果两个房子内容相同(equals为 true),它们必须在同一个小区/楼栋(hashCode相等),否则在“房产管理系统”中会放到不同分区,永远找不到对方。

📊 关键对比表:==vsequals()

维度==运算符equals()方法
基本类型比较数值是否相等不能用于基本类型(编译报错)
引用类型比较内存地址(是否同一对象)默认比较地址;StringInteger等重写后比较内容
能否重写不能(运算符,语法固定)能(Object 的方法,可重写)
调用方式直接a == ba.equals(b)(需判空,否则 NPE)
典型场景判断是否同一个对象;基本类型数值比较判断业务内容是否相等(如两个用户 ID 相同)

💣 三大常见坑点

坑1:equals()调用时未判空

Strings=null;System.out.println(s.equals("hello"));// NullPointerException!

正确"hello".equals(s)Objects.equals(s, "hello")

坑2:Integer==equals()混用

Integera=100,b=100;Integerc=200,d=200;System.out.println(a==b);// true(缓存)System.out.println(c==d);// false(超出缓存)System.out.println(c.equals(d));// true

正确:包装类比较数值一律用equals()

坑3:只重写equals(),不重写hashCode()→ HashMap 灾难

classUser{Stringname;User(Stringname){this.name=name;}@Overridepublicbooleanequals(Objectobj){if(this==obj)returntrue;if(!(objinstanceofUser))returnfalse;returnname.equals(((User)obj).name);}// ❌ 没有重写 hashCode!}// 使用Map<User,String>map=newHashMap<>();Useru1=newUser("张三");Useru2=newUser("张三");map.put(u1,"数据");System.out.println(map.get(u2));// null

原因HashMap.get()先算hashCode定位桶,u1u2哈希不同,分到不同桶,equals()根本用不上。


🔍 面试官追问(重点!)

追问1:equals()==在 String 中具体怎么工作?

  • String重写了equals(),比较字符序列。
  • 字面量String s1 = "a"; String s2 = "a";会先去字符串常量池检查,有就复用,所以s1 == s2为 true。
  • new StringString s3 = new String("a");强制在堆中创建新对象,s1 == s3为 false。

追问2:重写hashCode()时应该遵循什么规则?现代 Java 推荐写法?

(三条规则 + 现代写法):

规则

  1. 同一对象多次调用hashCode()返回相同整数(前提是equals比较的信息没变)。
  2. 如果两个对象equals()返回 true,则hashCode()必须相等。
  3. 如果两个对象equals()返回 false,hashCode()可以相等(哈希碰撞),但为了性能尽量不等。

现代写法(推荐)

@OverridepublicinthashCode(){returnObjects.hash(name,age);// 简洁、不易错}

传统手动计算31 * result + ...的方法已被Objects.hash取代。

追问3:HashMap 中先比 hashCode 再比 equals 有什么好处?

  • 直接equals遍历所有元素,时间复杂度 O(n)。
  • hashCode分桶,平均 O(1) 定位到小范围,再equals确认。
  • 哈希码相等不保证对象相等,所以还要equals最终判定。

追问4:有没有办法从根本上避免手动重写equalshashCode

(现代 Java 最优解):

  • 使用record(Java 14+ 正式特性,Java 17+ LTS 普及)。
recordUser(Stringname,intage){}
  • record自动生成了equalshashCodetoString和构造器,完全基于组件值。
  • 强烈推荐:只要是数据载体类,优先使用record,从根本上杜绝“忘记重写 hashCode”的 Bug。

💻 可运行验证代码

importjava.util.HashMap;importjava.util.Map;importjava.util.Objects;publicclassEqualsHashCodeDemo{// 正确示例:使用 record(最简洁)recordUser(Stringname,intage){}// 错误示例:手写但忘记 hashCodestaticclassBadUser{Stringname;BadUser(Stringname){this.name=name;}@Overridepublicbooleanequals(Objectobj){if(!(objinstanceofBadUser))returnfalse;returnname.equals(((BadUser)obj).name);}// 没有重写 hashCode}publicstaticvoidmain(String[]args){// 使用 recordUseru1=newUser("张三",25);Useru2=newUser("张三",25);System.out.println("record equals: "+u1.equals(u2));// trueSystem.out.println("record hashCode: "+(u1.hashCode()==u2.hashCode()));// trueMap<User,String>map=newHashMap<>();map.put(u1,"数据");System.out.println("map.get(u2) = "+map.get(u2));// 数据// 错误示例BadUserbu1=newBadUser("李四");BadUserbu2=newBadUser("李四");Map<BadUser,String>badMap=newHashMap<>();badMap.put(bu1,"数据");System.out.println("只重写equals不重写hashCode: "+badMap.get(bu2));// null}}

❓ 评论区挑战

问题:下面代码的输出是什么?为什么?

Strings1=newString("java");Strings2=newString("java");System.out.println(s1==s2);System.out.println(s1.equals(s2));

A. true / false
B. false / true
C. false / false
D. true / true

投票区已开启,欢迎留下你的答案和理由。


《面试官:Java 中的基本类型和引用类型有什么区别?== 比较它们时行为一样吗?》 评论区挑战问题

Integerx=null;inty=10;System.out.println(x==y);

A. false
B. true
C. 编译报错
D. 运行时抛出异常


✅ 答案公布

正确答案:D. 运行时抛出异常

解析

你答对了么?如果答案错误,请温习《面试官:Java 中的基本类型和引用类型有什么区别?== 比较它们时行为一样吗?》 文章中相关内容。


📚 系列导航


💬你在实际开发中遇到过因为没重写hashCode()导致的诡异 Bug 吗?或者已经用上了record彻底告别这个坑?欢迎评论区分享你的故事。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询