equals与hashcode必须协同工作,重写equals时必须同步重写hashcode;二者需满足:相等对象哈希码必相同、哈希码不同则必不相等、参与比较的字段不变则哈希码不变。

Java中equals与hashCode不是两个孤立方法,而是一对必须协同工作的契约伙伴。面试常考,线上出bug也常因它们失配——核心不在“会不会写”,而在“懂不懂为什么必须这样写”。
equals的默认行为和重写前提
Object类中的equals只做一件事:this == obj,即比较内存地址。它不看字段、不比内容,只认“是不是同一个对象”。这意味着:如果你希望两个内容相同但不同实例的对象被视为相等(比如两个User("Alice", 25)),就必须重写equals。
重写时需严格遵守五条规则(JavaDoc明确定义):
- 自反性:x.equals(x) 必须返回true
- 对称性:x.equals(y)为true → y.equals(x)也必须为true
- 传递性:x.equals(y)且y.equals(z)为true → x.equals(z)必须为true
- 一致性:多次调用结果不变(除非参与比较的字段被修改)
- 非空性:x.equals(null)永远返回false
hashCode的底层作用与默认实现
hashCode是native方法,在HotSpot中默认基于对象头(Mark Word)生成,与对象生命周期绑定——一旦生成,在对象存活期内绝不变化。它不是内存地址,但和对象身份强相关。
它的存在意义只有一个:给哈希容器(HashMap/HashSet)提供O(1)级定位能力。具体流程是:
- put时:先算key的
hashCode→ 映射到数组下标(桶)→ 再用equals在该桶内精确比对 - get时:同样先算
hashCode快速跳转到桶 → 再用equals确认目标元素
没有hashCode,哈希表就退化成遍历查找;没有equals,就无法解决哈希冲突。
二者必须绑定的三大契约
Java规范强制规定以下三条不可违背:
- 如果
obj1.equals(obj2) == true,那么obj1.hashCode() == obj2.hashCode()必须成立(否则HashMap根本找不到你存进去的key) - 如果
obj1.hashCode() != obj2.hashCode(),那obj1.equals(obj2)一定为false(这是性能保障,能跳过无效比对) - 只要参与
equals判断的字段没变,hashCode值在整个对象生命周期内必须保持不变(例如,把name设为key字段,就不能让name可变后还用它算hash)
典型避坑场景与修复方式
最常见错误是只重写equals,不碰hashCode。后果直观看就是:
- HashSet里能add两次相同业务含义的对象(重复)
- HashMap用新构造的key get不到已存的value(返回null)
- ConcurrentHashMap或LinkedHashSet中行为异常,排查困难
修复原则很明确:参与equals比较的字段,必须全部用于计算hashCode。推荐用Objects.hash(f1, f2, f3),安全、简洁、自动处理null。
额外提醒:若类设计为可变(字段会改),又打算用作Map key或Set元素,应避免将可变字段纳入equals/hashCode逻辑——否则哈希码中途改变,对象在集合中就“消失”了(既找不到,也删不掉)。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











