验证equals与hashcode契约需严格检查自反性、对称性、传递性、一致性及null比较,并确保相等对象hashcode相同;应构造同内容不同实例断言二者一致,辅以equalsverifier或hashset测试,避免字段遗漏、空指针及语义不一致等陷阱。

要验证 equals 与 hashCode 是否满足契约关系,核心是检查三条基本规则是否被严格遵守:自反性、对称性、传递性(equals),以及 相等对象必须有相同 hashCode(关键契约)。测试不是靠单个用例,而是构造有针对性的场景并断言行为。
1. 先确保 equals 满足基本等价关系
用 JUnit 编写多个断言,覆盖所有边界情况:
-
自反性:
obj.equals(obj)必须返回true(包括null检查) -
对称性:若
a.equals(b)为true,则b.equals(a)也必须为true -
传递性:若
a.equals(b)且b.equals(c)为true,则a.equals(c)也应为true -
一致性:多次调用
equals(对象状态未变)结果不能波动 -
与 null 比较:任何非 null 对象调用
obj.equals(null)必须返回false
2. 重点验证 equals–hashCode 契约:相等必同 hash
这是最容易出错的一环。需显式构造逻辑上 equals 为 true 的对象对,并断言其 hashCode() 相等:
- 创建两个内容相同但内存地址不同的实例(如通过 new 或 builder)
- 断言
a.equals(b) == true,再断言a.hashCode() == b.hashCode() - 特别注意浮点数、集合、嵌套对象等字段——若
equals比较用了Double.equals(),hashCode就不能直接用doubleValue()计算
3. 利用工具辅助发现隐性问题
手动覆盖所有组合易遗漏,可用以下方式增强保障:
- 用 EqualsVerifier 库一键验证(支持泛型、继承、Lombok):
EqualsVerifier.simple().forClass(MyClass.class).verify(); - 将对象放入
HashSet或作为HashMapkey 测试实际行为:
创建两个equals为true的对象,加入同一HashSet,大小应为 1;若为 2,说明hashCode不一致 - 对含可变字段的类,确认修改后未破坏契约(例如修改后
hashCode变了但equals仍为true—— 这是允许的,但不推荐用于 Map key)
4. 避免典型陷阱
常见错误会直接导致契约失效:
-
hashCode中漏掉equals里参与比较的某个字段(尤其新增字段时) - 在
hashCode中使用了可能为null的引用字段但没做空安全处理(如直接调用field.hashCode()) - 在
equals中用了==比较字符串或包装类型,而hashCode用了.hashCode()—— 导致语义不一致 - 子类重写
equals但没同步重写hashCode,或违反里氏替换(比如加了额外约束却没调整 hash 计算)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











