构造方法、static 与初始化顺序:从语法到内存语义的全景图
大约 3 分钟
构造方法、static 与初始化顺序:从语法到内存语义的全景图
新手先看 · 一屏速览
- 初始化顺序:静态字段/静态块 → 实例字段/实例初始化块 → 构造方法
- 第一行必须是 this(...) 或 super(...);未显式写 super 时编译器自动加
super() - final 字段一旦在构造完成后可见,跨线程读取是安全的(正确发布前提下)
1. 字段初始化与初始化块
class A {
static int S = initS();
static { System.out.println("static block"); }
int x = initX();
{ System.out.println("instance init block"); }
A(){ System.out.println("constructor"); }
static int initS(){ System.out.println("init S"); return 1; }
int initX(){ System.out.println("init x"); return 10; }
}输出顺序:加载类时先执行静态部分;创建对象时按“字段初始化→实例块→构造方法”进行。
2. this(...) 与 super(...)
- 构造方法第一行只能是
this(...)或super(...);两者二选一 this(...)链最终必须调用到某个构造方法内的super(...)
class B {
B(){ this(0); }
B(int x){ super(); /* ... */ }
}3. final 字段与可见性
- final 字段在构造函数结束前被正确写入,且对象不会在构造期间“逸出”,则其他线程读取到的值是可见且不变的
- 这也是不可变对象线程安全的基础
4. 初始化顺序陷阱
最近建一些几十个工作内推群,各大城市都有,群里目前已经收集了很多内推岗位,大厂、中厂、小厂、外包都有。 欢迎HR、开发、测试、运维和产品加入。

扫描下方微信,备注:网站+所在城市,即可拉你进工作内推群。

- 调用被子类覆盖的方法:在父类构造期间调用重写方法可能访问到子类未初始化的字段
class Base {
Base(){ foo(); }
void foo(){ System.out.println("base"); }
}
class Sub extends Base {
int n = 42;
@Override void foo(){ System.out.println(n); } // 可能打印 0
}工程建议:构造期间避免调用可被覆盖的方法;可将初始化逻辑放到 init() 并在构造末尾调用 final 方法。
5. 静态工厂与构建器
- 静态工厂:
of/create/newInstance命名更语义化,并可缓存与返回子类型 - 构建器(Builder):解决多参数构造的可读性与可选参数问题
6. 实战清单
- 避免在构造中启动线程或暴露
this;必要时使用工厂隔离 - 大对象分步初始化,保证失败可恢复与资源可回收
- 明确初始化顺序,避免在字段初始化中依赖未初始化的状态
7. 练习
- 设计一个不可变配置对象,含 Builder,要求:校验、默认值、只读暴露
- 构造链与继承:编写父子类,打印完整初始化顺序
参考代码(节选)
public final class Config {
private final int port; private final String host;
private Config(Builder b){ this.port=b.port; this.host=b.host; }
public static Builder builder(){ return new Builder(); }
public static final class Builder {
int port = 8080; String host = "127.0.0.1";
public Builder port(int p){ this.port=p; return this; }
public Builder host(String h){ this.host=h; return this; }
public Config build(){ return new Config(this); }
}
}