应避免在父类构造器中调用可被重写的方法,因其会导致子类字段读取默认值;解决方法是仅调用final或private方法、将多态逻辑延至构造后、使用工厂方法统一初始化、并添加运行时校验。

父类构造方法里调用被子类重写的方法,不是语法错误,但会导致子类字段读到默认值(null、0、false),而不是你期望的初始化值——这不是偶发问题,而是对象初始化顺序决定的确定性行为。解决核心是:不让多态逻辑出现在构造过程中。
只允许调用 final 或 private 方法
这是最直接、最安全的防护手段。final 和 private 方法无法被子类重写,父类构造器调用时永远执行本类版本,字段访问完全可控。
- 把所有构造器中调用的初始化方法,显式加上 final 修饰符,例如:
final void initConfig() { ... } - 如果方法仅用于内部初始化,直接改为 private ——语义更清晰,且天然不可重写
- 避免使用 protected 或 public 非 final 方法,哪怕当前没被重写,也埋下隐患
把多态逻辑推迟到构造完成之后
构造器的职责应严格限定为:分配内存、设置默认值、校验参数。所有依赖子类字段或需定制行为的操作,必须移出构造流程。
- 提供一个公共方法如
init()、configure()或start(),由使用者在new之后主动触发 - 该方法可以设计为 protected abstract,让子类自由实现,时机可控、字段已就绪
- 对关键资源(如配置加载、连接池初始化),采用惰性加载模式:用
Supplier或双重检查封装,首次访问才执行
用工厂方法统一控制实例生命周期
把“创建 + 初始化”收口到静态工厂方法中,降低误用概率,也便于类型约束和状态管理。
- 父类添加静态
async create()(或同步版):public static T create() { T inst = new T(); inst.init(); return inst; } - 子类可覆写
create(),但必须保证先调用super.init() - 配合 TypeScript 或 Java 的泛型+契约注解,让编译器提示“未调用 init 就使用实例”
加运行时防护机制
即使设计上做了隔离,仍建议增加一层轻量级校验,避免因调用方疏忽导致静默错误。
- 在父类中设私有标志位
private boolean initialized = false; -
init()执行成功后置为true - 关键业务方法开头加判断:
if (!initialized) throw new IllegalStateException("Call init() first"); - 子类
init()中不重置标志位,而是延续父类逻辑,防止重复初始化
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











