里氏替换原则(lsp)要求子类能无差别替换父类且程序行为不变:子类不得加强前置条件、削弱后置条件、破坏状态约束或新增未声明异常,核心是遵守父类行为契约而非仅语法兼容。

里氏替换原则(Liskov Substitution Principle, LSP)不是语法规则,而是一种行为契约:只要代码里用到了父类类型的地方,换成子类对象后,程序依然能按预期运行,不崩溃、不逻辑错、不违背业务常识。
它本质是“能换,且换了也没事”
比如你写了一个方法接收 Shape 类型参数,里面调用 getArea()。只要 Circle、Triangle 都是 Shape 的子类,那传进去就该算出合理面积——不能因为换了子类,结果返回 0、抛异常、或把面积算成周长。
关键不在“能不能编译通过”,而在“换完之后,调用方是否还敢放心用”。这背后是一套隐含约定:
- 子类方法的输入条件不能比父类更严(比如父类接受任意整数,子类不能只接受正数)
- 子类方法的输出结果不能比父类更弱(比如父类承诺返回非空列表,子类不能返回 null)
- 子类不能破坏父类维护的状态约束(比如父类保证“余额 ≥ 0”,子类转账后余额变成 -100 就违规)
- 子类不能额外抛出父类没声明的受检异常(
IOException父类没写,子类突然 throw,调用方就措手不及)
典型反例:正方形继承矩形
看似数学上成立,但代码中常出问题:
矩形有 setWidth() 和 setHeight(),各自独立;正方形重写这两个方法,让宽高同步更新。这时如果某段通用逻辑先设宽再设高,原意是拉伸矩形,换成正方形后却变成两次设边长——结果和预期完全不符。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
这不是“功能错了”,而是“换上去以后,原来对矩形成立的假设崩了”。所以更合理的做法是:用接口 Shape 抽象面积行为,让 Rectangle 和 Square 各自实现,不强行继承。
怎么判断是否符合 LSP
别只看类图关系,重点看使用场景:
- 把子类对象传给所有原本只依赖父类的方法,是否全部通过测试?
- 子类有没有悄悄改掉父类方法的语义?比如父类
save()是“保存并校验”,子类改成“仅写入不校验” - 父类文档里写的前置/后置条件,子类有没有偷偷绕过或放松?
- 子类新增的方法,会不会诱使调用方写出“必须是某个子类”的耦合代码?
它不是反对继承,而是反对“假继承”
真正符合 LSP 的继承,是子类在父类划定的契约内做事:可以加功能,不能改契约;可以更严谨,不能更随意;可以少抛异常,不能多抛;可以返回更具体的类型(协变返回),但不能返回不兼容的东西。
很多设计问题,根源不是“要不要继承”,而是“这个父类是否定义了足够稳定、可被信赖的行为契约”。契约稳了,替换才自然成立。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










