必须同时重写equals()和hashCode(),且参与计算的字段须不可变;仅重写其一将导致HashMap查找失败;推荐用Objects.equals()和Objects.hash()处理null,或直接使用Java record类。

关键在于让对象的 equals() 和 hashCode() 行为稳定一致,且参与计算的字段本身不可变。
必须同时重写 equals() 和 hashCode()
HashMap 判断键是否重复分两步:先比哈希值,再比内容。只重写其中一个会导致逻辑断裂:
- 只重 equals() 不重 hashCode():相同逻辑的对象可能被散列到不同桶里,
get()找不到; - 只重 hashCode() 不重 equals():哈希值相同但
equals()还是用默认引用比较,永远判不等,查不到结果。
参与计算的字段要真正代表“唯一性”且不可变
选哪些字段,取决于你业务上怎么定义“同一个键”。比如:
- 用户类用
userId唯一标识,那就只基于userId计算;加name反而会让同 ID 不同名变成不同键; - 订单类用
orderNo + storeId联合唯一,这两个字段就得一起参与; - 绝对避免用
status、lastModified这类可变字段——对象放进 Map 后一旦修改,哈希值就变了,原位置的数据等于“丢失”。
安全处理 null 和推荐写法
字段可能为 null 时,别直接调 field.equals(),容易空指针:
立即学习“Java免费学习笔记(深入)”;
- 用
Objects.equals(a, b)替代a.equals(b); - 用
Objects.hash(f1, f2, ...)生成哈希值,它对null返回 0,内部已做判空; - IDE(如 IntelliJ)自动生成
equals和hashCode最稳妥,避免手写漏判或逻辑错位。
Record 类是个省心选择
Java 14+ 的 record 天然适合当键:
- 字段默认
final,构造后不可变; - 编译器自动生成的
hashCode()等价于Objects.hash(components...),稳定可靠; - 只要 components 本身也稳定(如
String、Integer、LocalDate),就无需手动重写; - 警惕伪稳定情况:比如
record BadKey(List<string> tags)</string>——ArrayList是可变的,外部修改tags会破坏哈希一致性。


















