object类默认equals仅作引用比较,重写时必须确保自反性、对称性、传递性、一致性及null处理五条约定成立,否则集合类行为异常;需用this==obj快速返回true、instanceof判类型、统一字段语义、避免可变状态依赖,并同步重写hashcode。

Object 类默认的 equals 方法只做引用比较(this == obj),它天然满足自反性、对称性、传递性和一致性——但仅限于“同一对象”的场景。一旦你重写 equals,就必须主动保证这五条约定全部成立,否则集合类(如 HashSet、HashMap)行为会出错。
自反性:自己必须等于自己
任何非 null 对象调用 x.equals(x) 必须返回 true。常见错误是没做引用相等快速判断:
- 开头加
if (this == obj) return true;,直接拦截自身比较 - 如果跳过这步,而后续字段对比逻辑又意外返回
false(比如用了未初始化的变量),就破坏了自反性 - null 值不参与自反性检查,
x.equals(null)规定必须为false
对称性:x.equals(y) 和 y.equals(x) 结果一致
最容易被继承和跨类型比较破坏。典型反例是让 CaseInsensitiveString 和普通 String 单向兼容:
-
cis.equals("polish")返回true,但"polish".equals(cis)会调用String.equals(),因类型不匹配返回false - 解决办法:要么拒绝与其他类型交互(只接受同类型),要么双方都支持互操作(但需谨慎设计)
- 重写时统一用
instanceof判断,避免用getClass() == obj.getClass()导致子类失效,除非明确要求严格类型匹配
传递性:A=B 且 B=C ⇒ A=C
字段组合方式不当容易打破。例如按“忽略大小写”和“忽略空格”分别实现两个 equals,混用就会出问题:
- 推荐做法:所有参与比较的字段使用统一语义(比如全转小写再比,或全 trim 后再比)
- 若涉及继承,父类字段已比较,子类新增字段应只在类型完全匹配时才参与(否则可能破坏传递性)
- 浮点数、BigDecimal 等需用
equals()而非==,避免精度误差引发传递断裂
一致性:只要对象不变,结果就不变
核心是“比较所依赖的字段不能在比较过程中被修改”。这不是线程安全要求,而是逻辑稳定性要求:
- 不要在
equals里调用可能改变对象状态的方法(比如懒加载触发 setter) - 避免依赖可变字段(如
ArrayList实例)作比较;若必须,确保其内容在对象生命周期内不变,或使用不可变包装(如Collections.unmodifiableList) - 时间戳、计数器类字段通常不适合放入 equals,除非业务明确要求按“创建时刻”判定相等
不复杂但容易忽略:每一条约定背后都有真实运行时后果——HashSet.add() 可能漏加、HashMap.get() 可能查不到、list.contains() 返回错误结果。写完 equals,务必同步重写 hashCode,并用单元测试覆盖自反、对称、传递、null 等边界 case。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











