继承与多态:动态分派、LSP 与组合优先的实践指南
大约 3 分钟
继承与多态:动态分派、LSP 与组合优先的实践指南
新手先看 · 一屏速览
@Override的方法在运行期按“实际对象类型”调用(动态分派)- 构造方法内的调用不是多态安全的:父类构造期调用可被覆盖方法有风险
- 优先组合、谨慎继承;继承需满足 LSP(可替换)
1. 动态分派与方法覆盖
class Animal { String sound(){ return "??"; } }
class Dog extends Animal { @Override String sound(){ return "woof"; } }
class Cat extends Animal { @Override String sound(){ return "meow"; } }
Animal a = new Dog();
System.out.println(a.sound()); // woof(按实际类型 Dog 调用)- 多态核心:父类引用指向子类实例,调用由运行时决定
2. 构造期的“非虚调用”陷阱
- 父类构造期间,如果调用了子类覆盖的方法,子类字段可能尚未初始化,行为不确定
- 工程建议:构造期间只调用
private/final或static方法;覆盖点延后到init()阶段
3. 协变返回类型与访问控制
- 协变返回:子类覆盖方法可返回更具体的类型(Java 5+)
class Base { Number v(){ return 1; } }
class Sub extends Base { @Override Integer v(){ return 1; } }- 访问控制:子类覆盖方法不能缩小可见性(public→protected 不允许)
4. Liskov 替换原则(LSP)
- 子类对象必须能替换父类对象而不改变程序正确性
- 表现为:不破坏不变式;不扩大前置条件;不收紧后置条件;异常语义不出戏
5. 组合优先与扩展点
最近建一些几十个工作内推群,各大城市都有,群里目前已经收集了很多内推岗位,大厂、中厂、小厂、外包都有。 欢迎HR、开发、测试、运维和产品加入。

扫描下方微信,备注:网站+所在城市,即可拉你进工作内推群。

- 用组合将变化点独立为接口或策略,通过注入替代继承层级
interface Formatter { String format(String s); }
class Upper implements Formatter { public String format(String s){ return s.toUpperCase(); } }
class Printer { private final Formatter f; Printer(Formatter f){ this.f=f; } String print(String s){ return f.format(s); } }- 优势:扩展不破坏封装;测试替身容易;避免多层继承的复杂度
6. sealed classes(Java 17)
- 声明一个受限继承层次,只允许特定子类
public sealed class Shape permits Circle, Rect {}
public final class Circle extends Shape {}
public final class Rect extends Shape {}- 配合
switch表达式实现穷尽检查,减少默认分支
7. 实战清单与反模式
- 清单:覆盖方法显式
@Override;避免“神奇基类”;组合策略替代继承分支 - 反模式:子类随意改变父类约定;覆盖破坏不变式;父类构造中调用覆盖方法
8. 练习与思考
- 写一个
Shape层次,支持area(),比较“开放继承 vs sealed”可维护性 - 将一个“继承分支”重构为“策略组合”,测对测试与扩展的影响
- 设计一组
Formatter策略,支持多语言与本地化
