必须遵守三条契约:相等必同哈希、哈希相同不等于相等、对象未变结果不变;字段选择只用业务决定相等性的不可变字段,避免null和继承对称性问题;推荐用Objects.equals和Objects.hash实现。

面试时被问到 equals 与 hashCode 的设计规范,核心不是背定义,而是讲清“为什么这么规定”以及“不照做会出什么问题”。下面用实际开发中能立刻验证的逻辑来组织回答,清晰、有依据、不空泛。
必须遵守的三条硬性契约
Java 官方文档对这两个方法设定了不可打破的约定,违反任意一条,集合类行为就会失控:
-
相等必同哈希:如果
a.equals(b) == true,那么a.hashCode() == b.hashCode()必须成立。这是最常被忽略、也最致命的一条。只重写 equals 不重写 hashCode,HashMap 就永远找不到 key。 -
哈希相同不等于相等:
a.hashCode() == b.hashCode()成立,a.equals(b)可以是 true 也可以是 false。哈希冲突天然存在,不能指望靠 hashCode 唯一识别对象。 -
对象未变,结果不变:只要对象参与 equals 比较的字段没被修改,多次调用
hashCode()或equals()必须返回相同结果。这点直接影响缓存和集合稳定性,比如把对象放进 HashSet 后改了 id 字段,再查就找不到了。
重写时字段选择的关键原则
不是所有字段都要参与比较和哈希计算,选错字段会导致语义错误或性能问题:
-
只用“业务上决定相等性”的字段:比如 User 类中,如果业务认定“工号唯一且不可变”,那就只用
employeeId;如果允许同名同姓不同人,就不能只用name。 -
排除可变字段:如果字段后续可能被修改(如
lastLoginTime),就不该放进 equals 和 hashCode。否则对象加入 HashMap 后一旦修改,原位置就失效,再也取不到。 -
避免 null 引发 NPE:用
Objects.equals(a, b)替代a.equals(b),用Objects.hash(f1, f2, f3)替代手算,天然处理 null 安全。
常见错误写法及后果
这些是面试官最爱挖的坑,也是线上 bug 的高发区:
-
继承场景下 equals 失去对称性:父类 A 重写了 equals,子类 B 也重写但逻辑不兼容(比如 B 加了新字段判断),就可能出现
a.equals(b) == true但b.equals(a) == false,直接违反契约。 - 用随机数、时间戳、数据库主键生成 hashCode:导致同一对象每次调用 hashCode 返回不同值,HashMap 中的 key 彻底“隐身”。
- 在 equals 中调用可能抛异常的方法:比如调用远程服务或数据库查询来比对,不仅慢,还让集合操作变得不可预测。
推荐的写法模板(JDK 7+)
不用 Lombok 也能写出安全、简洁、可维护的实现:
-
equals:先判 null 和类型,再用
Objects.equals逐个比关键字段; -
hashCode:直接用
Objects.hash(field1, field2, ...),一行搞定; - 注意 final 修饰:参与比较的字段尽量声明为 final,从源头杜绝“改了字段导致哈希失效”的风险。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











