> 但我认为的是,因为当把一个对象放进哈希表的时候,我会默认它的 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 支持了