Java代码块执行顺序实战:掌握Java初始化的底层规律
Java代码块执行顺序由JVM类加载与对象创建阶段决定,静态代码块仅执行一次,构造代码块每次new都触发。继承场景下按先父后子、先静态后实例顺序执行,受声明位置约束。需注意编译期常量不触发初始化及多态陷阱。
Ja va 代码块的执行顺序,很多人靠死记硬背口诀,但说到底,它不是什么玄学,而是由 JVM 类加载和对象创建这两个阶段联手决定的。关键就两点:搞清“类初始化”和“对象初始化”的区别——前者一辈子只干一次,后者每次 new 都来一遍;而不管是哪个阶段,执行顺序都受到继承关系和声明位置的严格约束。

静态代码块:类首次被“用”到才执行,且仅此一次
静态代码块(static {})本质上是类级别的初始化代码。它什么时候跑?只有当这个类第一次被主动使用时——比如调静态方法、访问非 final 的静态字段、或者 new 它的实例——才会被触发。静态代码块和静态变量的显式赋值会被编译器打包进 方法,完全按照源码从上到下的顺序执行。
- 父类的静态部分永远在子类之前执行。哪怕你只写了一句
new Child(),也会先把 Parent 类加载并初始化完。 - 编译期常量(比如
public static final int X = 1;)不触发类初始化,因为它们直接被内联到调用处了,跟懒加载无关。 - 需要注意一个陷阱:如果静态变量初始化时直接 new 了本类的对象(例如
static A a = new A();),就会提前进入对象初始化流程,而此时类的静态初始化还没走完。这种循环依赖容易导致字段读到默认值,属于经典坑点。
构造代码块:每次 new 都触发,比构造方法更早
不带 static 的普通代码块({})属于实例初始化,每次调用构造器之前都会自动运行。它的执行时机在实例变量显式赋值之后、构造方法体之前。因为对所有构造器都生效,很适合提取那些每个构造器都要做的公共初始化逻辑。
- 如果类里写了多个构造代码块,按源码中的声明顺序依次执行。
- 可以访问实例变量、this,甚至外部作用域的局部变量(如果定义在方法内的话)。
- 它和实例变量直接赋值(比如
String name = "default";)处于同一层级,谁先谁后完全取决于它们出现的源码位置。
继承场景下的完整流水线:先父后子、先静态后实例
创建子类对象时,JVM 会按照一条固定链条推进,不可跳过、不可倒置:
- 父类静态变量默认值 → 父类静态变量显式赋值 + 静态块(从上到下)
- 子类静态变量默认值 → 子类静态变量显式赋值 + 静态块(从上到下)
- 父类实例变量默认值 → 父类实例变量显式赋值 + 构造块(从上到下)→ 父类构造方法体
- 子类实例变量默认值 → 子类实例变量显式赋值 + 构造块(从上到下)→ 子类构造方法体
这里有个关键点:父类构造器执行的时候,子类所有的实例字段都还是默认值(0、null、false)。如果父类构造器中不小心调用了被子类重写的方法,而那个方法又访问了子类字段,就会读到未初始化的状态——这是多态在构造阶段最常见的“翻车”场景。
普通代码块与方法内局部块:别傻傻分不清
写在类中、方法外的 {} 是实例初始化块,参与对象的创建流程;而写在方法内部的 {} 只是局部作用域块,只在方法执行到那一行时才运行,跟对象初始化没有半毛钱关系。
- 实例块只在 new 对象时执行一次,跟你调不调方法完全无关。
- 方法内的
{}不影响字段初始化,也不改变执行顺序,顶多用来限制变量的生命周期或提升代码可读性。 - 验证执行顺序最简单的方法:在每个代码块和构造器里加上带标识的日志(比如
[I] init block、[C] ctor),跑一次就能看到类加载、对象创建、方法调用三条线是怎么交错进行的。


































