子类构造中调用被重写方法打印null,是因为父类构造器执行时子类字段尚未初始化;Java对象创建按“分配内存→调用父类构造器→子类字段初始化→子类构造器剩余代码”顺序执行,多态导致父类构造中调用子类方法时访问到未初始化的字段。

子类构造中调用被重写方法,为何打印 null?
这是面向对象中一个经典且易被忽视的陷阱:当父类构造器内部调用了某个可被子类重写的方法,而该方法在子类中又被重写了,那么即使对象尚未完全构造完成,也会执行子类版本的方法——但此时子类自己的字段还未初始化。
以 Java 为例:
- 创建 Sub 实例时,JVM 首先调用 Base 的构造器
-
Base 构造器中执行
callName(),由于多态,实际调用的是 Sub 中重写的callName() - 但此时 Sub 类中声明的
private String baseName = "sub"还没执行(字段初始化发生在父类构造器返回之后) - 所以子类方法里访问
baseName,得到的是默认值null
初始化顺序决定行为结果
Java 对象创建过程中,字段初始化和构造器执行有严格时序:
- 分配内存(所有字段设为默认值:
null、0、false) - 调用父类构造器(递归向上)
- 执行当前类字段初始化语句(如
int a = 2;) - 执行当前类构造器剩余代码
关键点在于:第3步(子类字段显式初始化)在父类构造器执行完毕之后才发生。因此父类构造器中调用的任何被子类重写的方法,看到的都是未初始化或默认值状态的子类字段。
Python 中类似问题也存在
Python 虽无编译期字段声明,但逻辑一致:
- 若父类
__init__中调用了某方法,而子类重写了它 - 子类
__init__尚未运行到给自身属性赋值的语句时,该方法已被触发 - 此时访问
self.xxx可能引发AttributeError,或读到旧值/默认值
例如:父类初始化中调用 self.setup(),子类重写 setup 并依赖 self.config,但 self.config = {...} 在子类 __init__ 后半段才赋值——就会出错。
如何规避这个陷阱
核心原则:避免在父类构造器中调用可被子类重写的方法。
-
把可重写逻辑延迟到构造完成之后:提供一个
init()或start()方法,由使用者显式调用 -
将关键初始化方法设为
final(Java)或私有(Python):阻止子类覆盖,确保执行的是父类原始逻辑 - 用工厂方法替代直接 new:在对象完全构造后再注入行为,比如返回包装后的实例
- 文档明确警示:若必须保留该设计,应在父类 API 文档中强调“禁止在子类中重写此方法”
这不是 bug,而是语言机制的自然体现。理解初始化链条与多态绑定时机,才能写出健壮的继承结构。










