lsp要求子类重写父类方法时严格遵守行为契约:前置条件不收紧、后置条件不打折、不变式守牢;需通过替换测试、javadoc对照和调用方兼容性验证确保合规。

子类重写父类方法时,行为一致性不是靠“写得像”来保证的,而是靠严格守住父类定义的行为契约。LSP 不关心你有没有继承,只关心换上子类后,调用方代码是否还稳如泰山——不改一行,结果不变、异常不增、状态不乱。
守住方法契约的三条硬线
父类方法不是模板,是合同。子类重写时必须同时满足:
-
前置条件不能收紧:父类允许传入
int value(任意整数),子类就不能加校验说“必须大于100”;但可以放宽,比如接受Number或允许value == 0。 -
后置条件不能打折:父类文档写明“返回非 null 的 List”,子类就不能有时返回
null;但可以返回Collections.unmodifiableList(...)——这是增强,不违规。 -
不变式必须守牢:比如父类保证“账户余额 ≥ 0”,子类任何操作(包括重写的
withdraw())都不能让余额变负;哪怕抛异常,也得在破坏前拦截。
避开重写陷阱的实操判断法
只要子类覆盖了父类已实现的方法(非 abstract),就要立刻问自己:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- 调用方有没有可能因为子类多抛一个异常而崩溃?比如父类
save()只抛IOException,子类却加了ValidationException——调用方没 catch,就挂。 - 有没有地方依赖“设宽不影响高”这类隐含语义?像
Square继承Rectangle后,setWidth(100)顺带改了高度,所有按矩形逻辑布局的 UI 就错位。 - 子类里是不是出现了
if (this instanceof Square)或throw new UnsupportedOperationException()?这是 LSP 已经断裂的明确信号。
真正安全的重写方式
不是禁止重写,而是把重写控制在契约框架内:
-
只重写模板方法中的钩子:父类用
final锁住主流程(如processOrder()),只开放validate()、notify()等 protected 钩子——子类扩展逻辑,不动契约。 -
用组合替代强继承:比如
CreditCard不继承CashCard,而是持有一个CardBase实例,复用通用字段和校验,扣款逻辑走自己的策略。 -
接口优先,契约显性化:把行为定义在接口里(如
PaymentStrategy),每个实现类只对齐输入/输出/异常约定,不共享实现细节。
验证是否真合规的简单动作
别靠脑补,动手测:
- 把测试中所有
new Parent()替成new Child(),跑一遍原有测试——全过才算初步达标。 - 检查所有使用父类类型声明的变量或参数(如
Payment p = ...),确保换成子类实例后,调用链上没新增分支判断、没意外空指针、没状态错乱。 - 翻父类 Javadoc,逐条对照子类实现:输入范围、返回语义、异常列表、线程安全说明、null 处理——一条都不能漏。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










