Java对象初始化顺序为:静态成员→实例成员→构造器;final字段须在构造器结束前唯一赋值;构造器中禁用可重写方法;资源需显式销毁。

Java对象初始化顺序必须搞清:字段、构造器、初始化块谁先谁后
Java对象初始化不是按代码书写顺序执行的,而是有严格优先级。搞错顺序会导致字段为null或默认值,尤其在依赖注入或子类重写时出问题。
- 静态变量和静态初始化块(按出现顺序)→ 仅加载类时执行一次
- 实例变量赋值和实例初始化块(按出现顺序)→ 每次
new时执行,早于构造器 - 构造器体 → 最后执行,此时所有字段已“被初始化过”,但未必是预期值
public class InitOrder {
private String a = initA(); // 第二步执行
{ System.out.println("init block"); } // 第三步执行(与上同行)
public InitOrder() {
System.out.println("ctor: " + a); // 第四步:a 已是 "hello",但若 initA() 依赖未初始化的其他字段就危险
}
private String initA() {
System.out.println("initA called");
return "hello";
}
}
注意:initA() 在构造器之前调用,若它访问了子类尚未初始化的字段(比如被子类重写的getVal()),就会得到null或0——这是常见空指针源头。
final字段初始化必须满足“唯一赋值”规则,否则编译失败
final字段不是“只读”,而是“仅可赋值一次”。Java要求它在对象构造完成前(即构造器结束前)必须被明确赋值,且不能通过条件分支遗漏路径。
- 可在声明处直接赋值:
private final String id = UUID.randomUUID().toString(); - 可在每个构造器中赋值(包括所有重载构造器)
- 不可在普通方法、getter 或初始化块中“补赋值”
- 若使用
if-else分支,每个分支都必须给final字段赋值,否则编译报错:variable xxx might not have been initialized
public class FinalExample {
private final int code;
public FinalExample(boolean success) {
if (success) {
this.code = 200;
} else {
this.code = 500; // 必须有,否则编译失败
}
}
// 以下写法非法:
// public FinalExample() { } // 缺少对 code 的赋值
}
避免在构造器中调用可被重写的方法
这是JVM规范决定的:子类对象创建时,父类构造器先运行,但此时子类字段还未初始化,而this引用已是子类类型。如果父类构造器调用了protected或public方法,实际执行的是子类重写版本——但子类字段仍是默认值。
立即学习“Java免费学习笔记(深入)”;
- 现象:子类
toString()返回null或0,日志里看到奇怪值 - 根本原因:
this指向子类实例,但子类构造器尚未运行,字段未初始化 - 修复方式:把方法改为
private或static,或用工厂方法替代构造器逻辑
class Parent {
public Parent() {
init(); // 危险!此处调用的是 Child.init()
}
public void init() { System.out.println("Parent.init"); }
}
class Child extends Parent {
private String data = "ready";
@Override
public void init() {
System.out.println("Child.init: " + data); // 打印 "Child.init: null"
}
}
对象生命周期管理要区分“创建”“使用”“销毁”三阶段
Java有GC,但不等于不用管资源释放。像InputStream、Connection、线程池等,必须显式关闭或回收,否则造成句柄泄漏、内存堆积或连接耗尽。
- 创建:推荐使用静态工厂(如
LocalDateTime.now())或构建器模式,避免构造器参数爆炸 - 使用:避免在对象内部缓存外部状态(如
ThreadLocal未清理)、避免长生命周期对象持有短生命周期对象引用 - 销毁:实现
AutoCloseable并用 try-with-resources;慎用finalize()(已弃用);对非堆资源(如MappedByteBuffer)需主动clean()
特别注意:Spring Bean 的@PreDestroy只在容器正常关闭时触发,进程被kill -9或崩溃时不会执行——关键清理逻辑应放在业务完成后的显式调用点。










