里氏替换原则要求子类能无感替换父类对象,即调用方不改代码、结果不变、异常不增、状态不乱;须严守前置条件不收紧、后置条件不打折、不变式不破坏三条行为契约底线。

在多态设计中,里氏替换原则(LSP)不是让子类“看起来像”父类,而是确保子类对象能**无感替换父类对象**——调用方不改一行代码,运行结果不变、异常不增、状态不乱。关键不在语法通过,而在行为契约的严守。
守住方法的行为契约
父类方法是一份合同,子类重写时必须同步满足三条底线:
- 前置条件不能收紧:父类允许传入任意 String,子类就不能加校验说“必须非空且含数字”;但可以接受 CharSequence,这是放宽。
- 后置条件不能打折:父类文档写明“返回不可变 List”,子类就不能返回 new ArrayList();可返回 Collections.unmodifiableList(list),这是增强而非破坏。
- 不变式必须守牢:如父类 Account 约定“余额 ≥ 0”,子类 withdraw() 即使抛异常,也得在扣减前检查,绝不能让余额先变负再回滚。
验证是否真能安全替换
别靠推测,动手验证才是硬标准:
Java开发手册规约集合,基于阿里巴巴Java开发手册(嵩山版)。 涵盖7大维度:编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。 当用户需要:(1) 编写或审查Java代码 (2) 检查命名/代码规范 (3) 处理异常和日志 (4) 编写单元测试 (5) 安全编码 (6) 数据库设...
- 把所有测试中 new Parent() 替换为 new Child(),原有测试全绿,是初步达标信号。
- 检查调用方是否隐含依赖:比如父类 save() 声明只抛 IOException,子类若新增抛 ValidationException,而调用方没 catch,就直接崩溃。
- 搜索子类代码中是否出现 if (this instanceof XXX) 或 throw new UnsupportedOperationException() —— 这是 LSP 已断裂的明确红灯。
用设计降低违反风险
与其在继承里反复踩坑,不如从源头控制:
- 模板方法 + 钩子:父类用 final 锁住主流程(如 executePayment()),只开放 protected validate()、log() 等钩子,子类扩展逻辑但不动契约边界。
- 组合优于强继承:比如 PaymentProcessor 不继承 BankService,而是持有一个 BankClient 实例,扣款策略由自身决定,复用字段和连接能力即可。
- 接口先行,契约显性化:定义 PaymentStrategy 接口,明确输入、输出、异常范围;CashPayment 和 AlipayPayment 各自实现,互不继承,只对齐接口约定。
识别并重构危险继承
以下现象说明当前继承关系已失真,该动刀了:
- Square 继承 Rectangle 后,setWidth() 强制同步改 height,导致 resize() 等通用算法无限循环。
- Bird 父类有 fly() 方法,Penguin 子类重写为 throw new UnsupportedOperationException(),调用方一调就崩。
- 子类中频繁出现类型判断分支,如 if (obj.getClass() == CreditCard.class),说明多态已失效。
此时应抽象出 Shape 或 Flyable 接口,让 Rectangle/Square、Bird/Penguin 各自独立实现,或改用委派模式管理共用逻辑。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










