和 AI 交流的时候产生的一个问题:
AI 的大致结论是,对于业务类,如果存在明确的唯一标识,比如 ID ,那么应该重写这两个方法,这样在做哈希表之类的时候才能直接使用业务类。
但我认为的是,因为当把一个对象放进哈希表的时候,我会默认它的 hashcode 方法是默认的,也就是每个对象有唯一的哈希值。如果重写了 hashcode ,那么在使用的过程中如果不知道这个类复写了 hashcode ,那么就容易导致代码问题。
所以,我想知道做 java 后端的,你们一般会重写吗?
AI 的一部分原文:
如果抛开 “数量占比”,聚焦于「开发中需要重点设计、保证正确性的核心场景」,重写的频率会远高于 “不重写” 的核心场景,原因如下:
核心类必重写:所有承载业务数据的核心类(如 User 、Order 、Goods 等),几乎 100% 需要重写 —— 这是保证哈希集合正常工作、业务对象唯一性判断、序列化后比对等核心功能的前提,不存在 “可选” 的空间;
重写的 “重复性” 更高:一个项目中,数据承载类的数量虽少,但每个类的开发都必然包含 “重写 equals/hashCode” 这一步(无论是手动生成、Lombok 注解还是 Record 类),属于 “必做操作”;而不重写的类,只是 “无需额外操作”,并非 “主动开发行为”;
工具的普及佐证高频需求:Lombok 的 @EqualsAndHashCode 、Java 16 + 的 Record 类(默认重写)、IDE 自动生成功能,这些工具的广泛使用,本质是因为 “重写” 是开发中的高频需求,才会有大量工具来简化这一操作。
1
fulln 14 小时 30 分钟前
能理解 ai 说的重写的必要性, 直接用 Object 的原生 equals 是更直接和方便的做法, 但是我遇到的 99 都是手动拿出来 id 字段做对比的, 这时候重写等于没重写一样。
|
2
encounter2017 13 小时 37 分钟前
> 但我认为的是,因为当把一个对象放进哈希表的时候,我会默认它的 hashcode 方法是默认的,也就是每个对象有唯一的哈希值。如果重写了 hashcode ,那么在使用的过程中如果不知道这个类复写了 hashcode ,那么就容易导致代码问题。
这句话有这么几个误解: 1. “每个对象有唯一的哈希值”,hashcode 只有 2^32 个取值方式 2. “复写了 hashcode ,那么就容易导致代码问题”,只要你不是乱实现,比如 hashCode(anything) = 1, 那不会有啥问题,对于 hashset 的使用场景,冲突了也无所谓(性能会劣化一些),实际会用 equals 兜底 然后重写 equals 必须重写 hashcode, 为啥你可以看下面这个例子就知道了 ```java jshell> import java.util.*; jshell> class User { ...> int id; ...> User(int id) { this.id = id;} ...> ...> @Override public boolean equals(Object o) { ...> return (o instanceof User u) && this.id == u.id; ...> } ...> // 故意不重写 hashCode() —— 这是错误示范 ...> } | 已创建 类 User jshell> var set = new HashSet<User>(); set ==> [] jshell> set.add(new User(1)); $5 ==> true jshell> System.out.println("contains(new User(1)) = " + set.contains(new User(1))); contains(new User(1)) = false jshell> System.out.println("equals? " + new User(1).equals(new User(1))); equals? true ``` 然后你如果用过 Record 就知道,调用方不知道是否重写不是风险点,相反它是语言/库的常态用法。 ```java import java.util.*; record User2(int id) {} var m = Map.of(new User2(1), "ok"); System.out.println(m.get(new User2(1))); // ok ``` 然后什么时候重写 equals: 你需要业务上的相等比较而不是内存地址的比较 比如判断 peronaA == personB, Person(age: Int, name: String) 其实就是比较 person.age 和 person.name 这两个字段 这种情况下重写 equals 必须重写 hashcode ,原因上面说了 简单总结下: 1. 默认 hashCode 不保证唯一(取值空间有限、也可能碰撞) 2. 重写 hashCode 本身不是风险点,风险来自 equals/hashCode 契约被破坏 3. 重写 equals 必须重写 hashCode ,否则 HashSet/HashMap 会出现“看起来相等但查不到”的现象 然后还有一个点: 作为 HashMap/HashSet 的 key 时,参与 equals/hashCode 的字段最好不可变;否则对象放进集合后字段变化,会导致后续 get/contains 失败。 而这些功能和可能踩坑的点 JVM 的 Record ( 2020 年首次 preview ) 都帮你实现了 。作为对比: Kotiln 1.0 版本在 2016 年作为 data class 的核心关键词支持 而这个功能是 Scala 1.0 早在 2004 年 1.0 发布时就作为 case class 支持了 |
3
location123 13 小时 22 分钟前
安卓仔 和数据相关的 重写好一点 比如 list map 查找等不会出错 该说不说 Kotlin 的数据类真好用
|
4
WngShhng OP @encounter2017 “复写容易导致问题”的意思是,如果不知道已经被复写,然后以为是默认实现,就容易导致问题。补充下
|
5
encounter2017 12 小时 8 分钟前
@WngShhng 我还是没太懂,没有场景干说很难理解,你方便具体举一个这种容易出问题的例子,方便理解下吗
|
6
WngShhng OP @encounter2017 这就是一个规范而已,很难举例。就是说,不同的对象在内存上分配的地址是不同的,那么很容易因此而认为它们是不同的对象,即便它们在业务上相等,比如相同的用户实体或者有相同的 ID 。
因为很多时候我们拿到一个对象的时候,比如三方框架里的对象,不会去看它有没有复写这两个方法,因此,如果它们被复写了,再按照默认的逻辑去处理,就会导致代码问题。 |
7
prosgtsr 11 小时 40 分钟前
1:保证哈希集合正常工作
我不会把对象实例作为 hashkey ,所以我不会重写,也不喜欢别人重写 2:业务对象唯一性判断 要对比对象时,我也支持在业务里对比需要对比的每个属性,而不是用 equals ,为什么呢?现在有一个类有四个属性,业务对比了四个属性,新人有一天需要再加一个属性,你觉得老业务需要对比第五个属性吗?还有,写这段老代码的人,会喜欢你这么写代码影响他的逻辑吗?影响到老逻辑造成老逻辑出现 bug 的话新人会负责吗? |
8
encounter2017 11 小时 6 分钟前
|
9
bbao 10 小时 56 分钟前
2009 年左右的日经贴,在 2025 年又见天日。
|
11
netabare 10 小时 30 分钟前 via iPhone
抛开业务、框架、Java 这些问题,equals 和 hashcode 的意义是什么?
我的理解是这是为了构建 equivalence 关系吧。 那么问题是,知道不知道 hashcode 重写,对于 equivalence 的构建和对比,会有影响吗? HashMap 也好,上游 caller site ,他们做对比的时候会关心 hashcode 是如何使用的,还是说这只是一个契约? 我从这个角度讲会觉得 equals+hashcode 必定是要一起出现的。 |
12
guyeu 10 小时 21 分钟前
重写的 hashCode/equals 方法应该是默认实现的上位替代,所以你的“在使用的过程中如果不知道这个类复写了 hashcode ,那么就容易导致代码问题”是不成立的。
而由于默认实现的天然缺陷(容易把两个逻辑同一的对象当作不同对象)——考虑经典面试题 Integer 池和`new String("")`——在可能作为 Map 的键或 Set 元素的场景中重写 hashCode/equals 是必要的,对于关键的数据对象(如包含 id 字段的 User 实体类),你无法预见它们的使用场景,因此当然应该重写。 |
13
xuhengjs 9 小时 53 分钟前
非必要不用对象最 key 就是了,没事别去重写 hashcode/equals
|