自反性要求x.equals(x)恒为true,一致性要求相同输入下equals结果永不变化;二者缺一不可,否则hashset.contains()等集合操作失效,需用objects.equals并确保字段final或不可变。

保证自反性和一致性,是重写 equals 方法时最基础也最关键的两环。这两条不满足,集合操作(如 HashSet.contains()、ArrayList.indexOf())会当场失效——比如对象明明就在集合里,却查不到。
自反性:确保 x.equals(x) 永远为 true
自反性不是“理论上应该”,而是运行时必须成立的硬约束。一旦失败,集合底层在遍历或查找自身时就会误判。
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
-
常见破环场景:在比较字段前做了非对称处理,例如只对入参调用
trim()或toLowerCase(),却忘了对this做同样操作 -
典型错误写法:
if (!this.name.equals(other.name.trim()))—— 当this.name含空格而other是自己时,other.name.trim()被处理了,this.name却没 trim,导致p.equals(p)返回false -
安全做法:统一预处理,或直接使用
Objects.equals(this.name, other.name)—— 它内部自动处理 null、空字符串和相等情况,天然保障自反性
一致性:相同输入,结果永不变化
一致性要求:只要参与比较的字段没被修改,无论调用多少次 x.equals(y),结果必须完全一样。它不是性能优化问题,而是语义可靠性问题。
-
风险来源:在
equals中引入外部可变状态,比如依赖当前时间、随机数、缓存值、或未同步的共享变量 -
隐蔽陷阱示例:在比较逻辑中调用
new Date().getTime(),或读取一个可能被其他线程修改的 volatile 字段(且该字段未纳入 equals 判定主干) -
可靠写法要点:
- 只基于对象自身的 final 或不可变字段做判断
- 避免调用可能产生副作用或返回波动值的方法(如
getComputedScore()若内部依赖系统时钟) - 若必须用计算值,确保其纯函数性(输入确定 → 输出确定),且不随外部状态漂移
顺带提醒:自反性与一致性的协同验证
两者常一起出问题。例如,你用 Objects.equals(name, other.name) 保住了自反性,但如果 name 是一个可变的 StringBuilder 字段,且后续被外部修改,那么多次调用 equals 就可能返回不同结果——一致性就崩了。
- 推荐把参与 equals 比较的字段声明为
final - 若字段不能 final(如 JPA 实体),确保业务层不随意修改这些字段,或在修改后明确触发状态同步
- 单元测试中可加断言:
assertTrue(obj.equals(obj))和assertEquals(obj.equals(other), obj.equals(other))(连调两次比对)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










