里氏替换原则(lsp)用于判断是否该用继承,核心是子类能否无缝替换父类且行为一致;它要求子类不增强前置条件、不削弱后置条件、不新增未声明异常、不破坏不变量。

里氏替换原则(Liskov Substitution Principle, LSP)不是教你怎么“写继承”,而是帮你判断“该不该用继承”——它用行为一致性作为标尺,划清合理继承与伪继承的边界。
看子类是否真能“无缝替换”父类
关键不是语法上能否编译通过,而是运行时逻辑是否可靠。比如:
- 客户端传入 Rectangle 类型参数调用
resize(),若实际传的是 Square 子类,结果因宽高强制同步导致死循环,就已违反 LSP - 父类方法声明返回
List<string></string>,子类重写后返回ArrayList<string></string>(协变允许),但若返回null或抛出UnsupportedOperationException,就破坏了调用方预期 - 父类
save(File f)接受任意File对象,子类却要求文件必须存在且可写——前置条件被加强,父类安全的调用在子类下可能直接失败
警惕三类典型“伪继承”信号
出现以下情况,说明继承关系大概率设计失当:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 子类中大量方法体是
throw new UnsupportedOperationException()或return null - 客户端代码频繁使用
instanceof或强制转型来区分类型、再做不同处理 - 单元测试覆盖父类行为时,某个子类反复失败,原因不是 bug,而是语义冲突(如正方形无法独立设宽高)
用组合+接口替代“is-a”错觉
当子类只是“有某种能力”,而非真正意义上的“是一种”,就该重构:
- 把父类中不通用的行为拆成接口,例如
Flyable、Swimmable,让Bird实现前者,Duck同时实现两者,Ostrich则只实现后者 - 用组合代替继承:原
Ostrich extends Bird导致fly()无意义;改为Bird持有FlightBehavior,Ostrich注入NullFlightBehavior - 保留继承仅用于真正满足“is-a”且行为可互换的场景,例如
ArrayList和LinkedList都是List的合规实现
设计时守住四条契约底线
LSP 要求子类对父类承诺的契约保持尊重:
- 前置条件不增强:子类方法接受的输入范围 ≥ 父类(不能更窄)
- 后置条件不削弱:子类方法保证的输出效果 ≥ 父类(不能更弱)
- 异常不新增:不能抛出父类方法签名中未声明的检查异常
- 不变量不破坏:对象状态约束(如“宽高始终非负”)在子类中仍成立或更严格
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










