里氏替换原则要求子类对象可无缝替代父类对象,行为契约必须严格遵守:前置条件不能加强,后置条件不能减弱,不变量必须保持。

里氏替换原则(LSP)不是“子类能编译通过地继承父类”,而是“子类对象塞进所有用父类的地方,程序照样跑对、不崩、不骗人”。核心约束不在语法,而在行为契约的严守。
前置条件不能收得更紧
父类方法承诺能处理的输入范围,子类必须全盘接受,不能挑三拣四。比如父类process(int x)文档写明“支持-100到100”,子类就不能在内部加个if (x ——调用方传个-5,父类好好的,换成子类就炸了。
- 父类接受
null,子类不能拒绝null - 父类说“支持任意长度字符串”,子类不能只认长度≤10的
- 放宽参数类型(如父类用
List,子类用Collection)可以;但收紧(如强制非空、强制具体实现类)一定违规
后置条件不能放得更松
父类方法承诺的输出效果,子类必须做到甚至更好,不能打折扣。它保证返回非null列表?子类不能返null。它说“修改后状态一致”?子类不能让对象处于半更新的脏状态。
- 父类返回
int,子类不能返long或null(除非父类已明确允许null) - 父类文档写“调用后对象进入有效校验态”,子类不能跳过校验直接落库
- 协变返回允许(如父类返
Shape,子类返Circle),但返回类型必须是父类声明类型的子类型,且语义不变
异常体系必须守界
父类方法没声明会抛的受检异常(throws IOException),子类绝不能擅自加;父类明确声明的异常,子类可以不抛,但不能抛更宽泛或未预告的新异常。
- 父类
save()没写throws,子类突然抛DatabaseException——调用方毫无防备 - 父类声明
throws ValidationException,子类可选择不抛,或抛其子类EmptyNameException,但不能抛RuntimeException以外的全新受检异常
隐含状态与语义不得漂移
比代码更危险的是注释和文档里的“潜规则”。父类setX()隐含“不影响Y字段”,子类重写时若顺手把Y也清零,调用方按原逻辑走就会出错。正方形继承矩形就是典型:父类设宽、设高是两个独立操作;子类一设宽就同步改高,表面功能都在,实际行为契约崩塌。
- 父类保证“余额≥0”,子类转账后出现负数,违反状态约束
- 父类
getArea()被调用时从不改变对象状态,子类重写后偷偷缓存并修改内部字段,破坏无副作用预期 - 父类方法标“线程安全”,子类实现变成非线程安全,哪怕签名、返回值完全一样,也违规
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











