Java equals 与 hashCode
对象相等性与哈希契约:何时覆写 equals/hashCode,如何避免哈希容器中的丢失与重复。
#concept
#growing
#dev
#java
[!info] related notes
Java equals 与 hashCode
这个概念为什么出现
业务常问“是不是同一笔订单/同一个用户”。对引用,== 判断是否指向同一对象;基本类型的 == 比较值。Object.equals 默认也按身份比较,覆写后才能表达所需的值/业务相等。HashMap/HashSet 还依赖 hashCode 分桶——破坏契约会导致“放得进、取不出”。
[!abstract] 一句话理解
equals定义业务相等;hashCode必须与 equals 一致;哈希容器先 hash 后 equals,契约破坏即逻辑漏洞。
最小可运行示例
场景:优惠券核销用“券码”作为集合去重键
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
final class Coupon {
private final String code;
Coupon(String code) {
this.code = Objects.requireNonNull(code);
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Coupon c)) return false;
return code.equals(c.code);
}
@Override public int hashCode() {
return code.hashCode();
}
@Override public String toString() { return code; }
}
public class CouponDedup {
public static void main(String[] args) {
Set<Coupon> set = new HashSet<>();
set.add(new Coupon("SAVE50"));
set.add(new Coupon("SAVE50")); // 业务相等,应去重
System.out.println(set.size()); // 1
System.out.println(set.contains(new Coupon("SAVE50"))); // true
}
}
建议运行:
javac CouponDedup.java && java CouponDedup
期望输出:
1
true
结合场景再看三个关注点
- 只覆写 equals 不覆写 hashCode 会让 HashSet 失效。
- 相等字段必须稳定;可变键进哈希容器是坑。
- instanceof 与 getClass 各有适用条件:前者允许子类型参与,后者要求精确类;需结合继承与对称性设计,本例 final 类没有子类问题。
核心概念与准确模型
1. equals 契约(Object API)
- 自反、对称、传递、一致
- 对
null返回 false
2. hashCode 契约
equals为 true ⇒hashCode必须相等- 不相等对象 hash 可碰撞,但应尽量分散
3. == / equals / Objects.equals
| 场景 | 用法 |
|---|---|
| 同一实例 | == |
| 业务相等 | equals |
| 两侧可能 null | Objects.equals(a,b) |
4. Record 与自动生成
record默认按组件生成 equals/hashCode(见 Record 篇)
边界情况与反直觉行为
- 继承体系中 equals 对称性极难维护——值对象宜
final。 - 数组的 equals 默认按引用身份,内容比较用 Arrays.equals/deepEquals,并配套对应的哈希方法。
- 浮点要分层:原始 double 的 NaN == NaN 为 false、+0.0 == -0.0 为 true;Double.equals 对 NaN 视为相等,却区分正负零。\
- 字符串比较注意大小写/Locale;Objects.equals 仅做空安全委派,不会自动深比较数组。\
常见误区
[!warning] 常见误区:用可变字段做 equals 且作 HashMap key 变更字段不会让 HashMap 自动搬迁条目;后续按新哈希查找可能找不到,表现为“丢失”。
工程实践
- 值对象:
final类 + 不可变字段 + 成对覆写。 - 实体对象:常按 ID 相等,谨慎混用。
- 用 IDE/Record 生成,再人工审查空值与数组。
- 单测覆盖对称性与哈希集合行为。
本节总结
- 相等是业务定义,不是引用默认
- hashCode 与 equals 一体
- 不可变值对象最省心
自测题
- 为何 equals 相同必须 hashCode 相同?
Objects.equals解决什么?
参考答案
- 哈希容器用 hash 定位桶;不一致会导致找不到相等对象。
- 空安全的相等比较,避免
a.equals(b)在 a 为 null 时 NPE。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Object.equals / hashCode | API | 契约说明 |
| JLS Equality Operators | 规范 | 数值与引用 == |
| Effective Java(条目 equals/hashCode) | 权威书 | 实践规则 |