面试官问:==和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()方法 |
|---|---|---|
| 基本类型 | 比较数值是否相等 | 不能用于基本类型(编译报错) |
| 引用类型 | 比较内存地址(是否同一对象) | 默认比较地址;String、Integer等重写后比较内容 |
| 能否重写 | 不能(运算符,语法固定) | 能(Object 的方法,可重写) |
| 调用方式 | 直接a == b | a.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定位桶,u1和u2哈希不同,分到不同桶,equals()根本用不上。
🔍 面试官追问(重点!)
追问1:equals()和==在 String 中具体怎么工作?
答:
String重写了equals(),比较字符序列。- 字面量:
String s1 = "a"; String s2 = "a";会先去字符串常量池检查,有就复用,所以s1 == s2为 true。 - new String:
String s3 = new String("a");强制在堆中创建新对象,s1 == s3为 false。
追问2:重写hashCode()时应该遵循什么规则?现代 Java 推荐写法?
答(三条规则 + 现代写法):
规则:
- 同一对象多次调用
hashCode()返回相同整数(前提是equals比较的信息没变)。 - 如果两个对象
equals()返回 true,则hashCode()必须相等。 - 如果两个对象
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:有没有办法从根本上避免手动重写equals和hashCode?
答(现代 Java 最优解):
- 使用
record(Java 14+ 正式特性,Java 17+ LTS 普及)。
recordUser(Stringname,intage){}record自动生成了equals、hashCode、toString和构造器,完全基于组件值。- 强烈推荐:只要是数据载体类,优先使用
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. 运行时抛出异常
解析:
x == y触发自动拆箱,即x.intValue() == 10。- 由于
x为null,调用null.intValue()抛出NullPointerException。 - 因此程序不会输出 false 或 true,而是直接抛出异常。
你答对了么?如果答案错误,请温习《面试官:Java 中的基本类型和引用类型有什么区别?== 比较它们时行为一样吗?》 文章中相关内容。
📚 系列导航
- 上一篇:面试官问:基本类型和引用类型到底有什么不同?
- 下一篇预告:面试官问:String、StringBuilder、StringBuffer有什么区别?(待发布)
- 全部85题目录:点击查看
💬你在实际开发中遇到过因为没重写
hashCode()导致的诡异 Bug 吗?或者已经用上了record彻底告别这个坑?欢迎评论区分享你的故事。