父类构造器中调用被子类重写的方法会导致子类字段未初始化就被访问,解决方法是仅调用final或private方法、将多态逻辑移至init()等显式方法、采用两段式构建,并借助ide和静态检查工具提前拦截风险。

父类构造方法里调用被子类重写的方法,会导致子类字段还没初始化就被访问——此时引用类型是 null,基本类型是 0 或 false。这不是偶发问题,而是每次执行都一样的确定性错误。解决的核心思路不是“提前初始化”,而是“避开这个时机”。
只允许在构造器中调用 final 或 private 方法
这是最直接、最可靠的语法级防护。只要方法声明为 final 或 private,子类就无法重写,父类构造器调用时永远执行本类版本,字段访问完全安全。
- 把所有被构造器调用的初始化方法显式加上
final修饰符,例如:final void validate() { ... } - 如果方法仅用于内部初始化,直接改为
private——语义更清晰,且天然不可重写 - 避免使用
protected或public非final方法,哪怕当前没被重写,也埋下隐患
把多态逻辑推迟到对象创建完成之后
构造器只负责分配内存、设置 final 字段、校验参数。所有依赖子类字段或需定制行为的操作,都应该移出构造流程,交给显式触发的方法。
- 提供一个
init()、setup()或configure()方法,由使用者在new之后主动调用 - 该方法可以是
protected abstract,让子类自由实现,时机可控、字段已就绪 - 对关键资源(如配置加载、连接池),采用惰性加载:用
Supplier或双重检查封装,首次访问才执行
用两段式构建替代“构造即就绪”惯性思维
放弃“new 出来就能直接用”的假设,把对象生命周期明确拆成“创建”和“准备”两个阶段。
- 父类构造器只做最低限度的事:调用
super()、记录传入参数(如 ID、配置对象) - 子类字段只声明,不赋值;所有有意义的初始化放在
setup()中完成 - 可在父类加防护:若
setup()未被调用就访问关键字段,抛出IllegalStateException
借助编译期和 IDE 工具提前拦截
光靠约定不可靠,要让问题在写代码时就暴露出来。
- Java 中将父类构造器内调用的方法声明为
final,子类无法重写,风险自然消失 - IDE(如 IntelliJ)启用
Constructor calls overridable method检查项,实时标红提醒 - 配合 SonarLint 或自定义 lint 规则,扫描构造器内对非私有方法的调用,标记为高危
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











