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()(已弃用);非堆资源(如MappedBytebufer)需要主动clean()
特别注意:Spring Bean 的@PreDestroy当容器正常关闭时,只触发过程kill -9或崩溃时不执行——关键清理逻辑应放在业务完成后的显式调用点。