← [ BACK_TO_NOTES ]

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

结合场景再看三个关注点

  1. 只覆写 equals 不覆写 hashCode 会让 HashSet 失效。
  2. 相等字段必须稳定;可变键进哈希容器是坑。
  3. instanceof 与 getClass 各有适用条件:前者允许子类型参与,后者要求精确类;需结合继承与对称性设计,本例 final 类没有子类问题。

核心概念与准确模型

1. equals 契约(Object API)

  • 自反、对称、传递、一致
  • 对 null 返回 false

2. hashCode 契约

  • equals 为 true ⇒ hashCode 必须相等
  • 不相等对象 hash 可碰撞,但应尽量分散

3. == / equals / Objects.equals

场景用法
同一实例==
业务相等equals
两侧可能 nullObjects.equals(a,b)

4. Record 与自动生成

  • record 默认按组件生成 equals/hashCode(见 Record 篇)

边界情况与反直觉行为

  1. 继承体系中 equals 对称性极难维护——值对象宜 final。
  2. 数组的 equals 默认按引用身份,内容比较用 Arrays.equals/deepEquals,并配套对应的哈希方法。
  3. 浮点要分层:原始 double 的 NaN == NaN 为 false、+0.0 == -0.0 为 true;Double.equals 对 NaN 视为相等,却区分正负零。\
  4. 字符串比较注意大小写/Locale;Objects.equals 仅做空安全委派,不会自动深比较数组。\

常见误区

[!warning] 常见误区:用可变字段做 equals 且作 HashMap key 变更字段不会让 HashMap 自动搬迁条目;后续按新哈希查找可能找不到,表现为“丢失”。

工程实践

  1. 值对象:final 类 + 不可变字段 + 成对覆写。
  2. 实体对象:常按 ID 相等,谨慎混用。
  3. 用 IDE/Record 生成,再人工审查空值与数组。
  4. 单测覆盖对称性与哈希集合行为。

本节总结

  • 相等是业务定义,不是引用默认
  • hashCode 与 equals 一体
  • 不可变值对象最省心

自测题

  1. 为何 equals 相同必须 hashCode 相同?
  2. Objects.equals 解决什么?
参考答案
  1. 哈希容器用 hash 定位桶;不一致会导致找不到相等对象。
  2. 空安全的相等比较,避免 a.equals(b) 在 a 为 null 时 NPE。

延伸阅读与资料来源

资料类型支撑内容
Object.equals / hashCodeAPI契约说明
JLS Equality Operators规范数值与引用 ==
Effective Java(条目 equals/hashCode)权威书实践规则
创建于 2026/7/15 更新于 2026/10/6
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本