java对象初始化顺序为:静态变量/块→实例变量默认值→实例变量显式赋值和实例块→构造方法体;构造方法非唯一入口,且调用可重写方法易致半初始化bug。

构造方法不是初始化的唯一入口
Java对象初始化过程里,构造方法只是其中一环,不是“第一道门”也不是“最后一道关”。真正顺序是:静态变量/静态块 → 实例变量默认值 → 实例变量显式赋值和实例块 → 构造方法体。很多人以为写了new MyClass()就只走构造方法,其实前面三步已经默默执行过了。
常见错误现象:NullPointerException出现在构造方法第一行,但问题出在实例块里调用了尚未初始化完成的this引用(比如注册监听器、启动线程),而此时字段还是null或默认值。
- 实例变量显式赋值(如
private List<string> list = new ArrayList();</string>)会在构造方法之前执行 - 多个
构造方法之间用this(...)调用时,被调用的那个构造方法仍会完整走完上述初始化流程 - 如果父类有带参构造且没写无参构造,子类
构造方法必须显式调用super(...),否则编译报错:Implicit super constructor is undefined
构造方法不能被继承,但可以被重载
构造方法没有返回类型,也不属于成员方法,所以子类不会继承父类的构造方法。这是很多初学者误以为“重写父类构造就能控制初始化逻辑”的根源。
使用场景:想让子类复用父类初始化逻辑,得靠super(...)显式委托;想提供多种创建方式,就在同一个类里重载多个构造方法,而不是靠继承。
立即学习“Java免费学习笔记(深入)”;
- 重载的
构造方法之间可用this(...)互相调用,但只能是第一句 -
private构造方法能防止外部实例化,常用于单例或工具类,但要注意反射仍可绕过(setAccessible(true)) - Lombok的
@AllArgsConstructor或@RequiredArgsConstructor生成的构造方法,会严格按字段声明顺序传参,字段顺序变了,调用方可能 silently 错位
构造方法里调用可重写方法是危险操作
在构造方法中调用public或protected方法,而该方法在子类中被重写,会导致子类方法在父类字段还没初始化完时就被执行——因为此时对象类型已是子类,但子类字段仍是默认值。
典型错误现象:子类重写了init(),父类构造方法末尾调用了它,结果init()里访问了子类的String config字段,得到null而非预期值。
- 避免在
构造方法中调用非private、非final的方法 - 若必须触发初始化逻辑,改用
private final方法,或把逻辑拆到工厂方法里(如MyClass.create(...)) - IDE通常会对这类调用标黄警告,提示“May cause initialization problems”
构造方法抛异常会让对象处于半初始化状态
一旦构造方法抛出异常,JVM会立即终止初始化流程,对象不会被分配给任何变量(哪怕你写了MyClass obj = new MyClass();),也不会进入GC可达性分析——它根本没“出生成功”。
但这不意味着安全:如果构造过程中已修改了外部状态(比如注册了静态监听器、打开了文件句柄、发出了网络请求),这些副作用不会自动回滚。
- 不要在
构造方法里做资源申请或外部交互,优先用工厂方法+明确的close()或init()生命周期管理 - 如果必须处理异常,确保所有副作用都可逆,或用try-with-resources包装可关闭资源
- 序列化反序列化时,
readObject()本质也是个“隐式构造方法”,同样存在半初始化风险
构造方法看着简单,但它卡在类加载、实例化、多态调用三者的交界处。最容易被忽略的是:它执行时,对象身份已确立(this存在),但字段未必就绪,子类也未必准备好——这种“将成未成”的状态,正是多数诡异 bug 的温床。








