要让equals在多态体系中符合里氏替换原则,必须用getClass()严格类型校验、子类复用super.equals()后再比较新增字段、hashCode同步覆盖所有参与比较的字段,并确保父类本身已合规。

要让 equals 在多态体系中重写后仍符合里氏替换原则(LSP),核心不是“子类能不能重写”,而是“重写后是否还守约”——即调用方用父类引用调用 equals 时,换成子类对象,行为、结果、异常、语义都不能变。
类型检查必须严格匹配 getClass(),不能用 instanceof
父类若用 instanceof 判断类型,子类就可能被“误认”为父类而跳过自身字段比较,导致不对称或不传递;更严重的是,父类允许与子类实例比较,子类却拒绝与父类比较,直接破坏 LSP。
正确做法是:所有层级都用 getClass() == obj.getClass() 做类型校验。这样保证:
- 父类实例只和同类型对象比较,不会接受子类实例
- 子类实例也只和同类型对象比较,不会接受父类或其他子类
- 对称性(
a.equals(b)⇔b.equals(a))、传递性自然成立
子类 equals 必须先复用父类逻辑,再扩展自身字段
子类新增字段参与相等判断时,不能绕过父类的字段校验。否则会出现:父类对象 a 和子类对象 b 比较返回 true(因父类逻辑未拦住),但 b.equals(a) 却返回 false(因子类校验失败)——违反对称性,LSP 失效。
立即学习“Java免费学习笔记(深入)”;
正确结构如下:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 先做
this == obj和obj == null || getClass() != obj.getClass()判断 - 再调用
super.equals(obj)—— 注意传原始obj,不是转型后的引用 - 最后比较子类独有字段,如
Objects.equals(extraField, that.extraField)
hashCode 必须与 equals 严格同步,且覆盖全部参与比较的字段
LSP 要求子类对象能安全放入基于哈希的集合(如 HashSet、HashMap)中,而这些集合依赖 hashCode 和 equals 的一致性。
常见违规:
- 子类重写了
equals加入新字段,但hashCode没包含它 → 相等对象哈希码不同 → 在HashSet中被当作不同元素 - 直接调用
super.hashCode(),而父类的hashCode没覆盖其全部equals字段 → 同样导致哈希不一致
正确做法:子类 hashCode 必须重新计算,把父类参与 equals 的字段 + 自身新增字段全部纳入,推荐用 Objects.hash(field1, field2, extraField)。
避免父类实现本身已违反 LSP 的情况
如果父类的 equals 已用 instanceof 或未做 getClass() 校验,子类仅靠调用 super.equals(obj) 无法修复问题——因为契约从根上就松动了。
此时不能妥协迁就,应:
- 推动父类修正为
getClass()方式,或 - 子类放弃复用
super.equals(),完整重写整个逻辑(含父类字段比较),确保自身满足 LSP
否则,整个继承链的相等性语义就是不可靠的,多态调用必然出错。

















