里氏替换原则(lsp)核心是子类必须能无缝替换父类且程序行为不变;关键看方法前置条件不收紧、后置条件不削弱、不新增未声明异常、不破坏契约,而非仅语法继承或名称相似。

里氏替换原则(Liskov Substitution Principle,LSP)不是讲“子类能不能继承父类”,而是问“子类替换了父类之后,程序还跑不跑得通、逻辑还对不对”。只要一换就出错、要加 instanceof 判断、或某子类测试总失败——那大概率是继承用错了。
关键看行为是否可替换,不看名字像不像
比如 Bird 类有 fly() 方法,Penguin 继承它却在 fly() 里抛异常,这违反 LSP。因为调用方写的是 makeBirdFly(new Bird()),本以为所有 Bird 都能飞;换成 Penguin 就崩了——不是代码有 bug,是设计契约被打破了。
真正满足 LSP 的继承,必须保证:
- 子类方法的前置条件不能比父类更严格(例如父类允许 null,子类不能突然校验非空)
- 后置条件不能比父类更弱(例如父类保证返回非 null,子类不能返回 null)
- 不抛出父类没声明的新检查异常
- 不破坏父类维护的关键不变量(如计数器只增不减)
常见违反信号,就是代码开始“打补丁”
以下情况说明继承关系已经失衡:
- 子类大量重写父类方法,内容却是 throw new UnsupportedOperationException() 或 return null
- 客户端必须用 if (bird instanceof Ostrich) 做分支处理
- 父类单元测试通过,但某个子类运行时反复失败,原因不是缺陷,而是语义冲突(比如 Square 继承 Rectangle 后,setWidth() 会连带改高度)
- 调用方为兼容不同子类,不断添加防御性判断或类型转换
重构方向:少用“is-a”,多用“can-do”
当“是一种”关系撑不住行为一致性时,优先考虑解耦:
- 把共性能力抽成接口(如 Flyable、Swimable),让能飞的实现 Flyable,能游的实现 Swimable
- 用组合代替继承:让 Bird 持有一个 FlightBehavior,Sparrow 设为 WingFlight,Ostrich 设为 NoFlight
- 保留继承仅用于真正满足“is-a + 行为可替换”的场景,比如 ArrayList 和 LinkedList 都是 List 的合理实现,传给任何接受 List 参数的方法都安全
验证是否真符合 LSP,就看这一条
把任意子类实例,直接传给所有原本只依赖父类或接口的方法——不改调用方代码、不加类型判断、不抛意外异常、结果和预期一致。能做到,才算落地了里氏替换原则。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











