Java类初始化顺序及静态/实例构造自调用递归差异问题
Java类初始化顺序与构造递归问题详解
一、先搞懂Java类的初始化顺序
咱们一步一步来拆解,Java的初始化分两大核心阶段:类加载初始化阶段(针对静态成员)和实例创建阶段(针对非静态成员),顺序是固定死的,千万别搞混:
类加载时的静态初始化(整个生命周期仅执行一次)
当类第一次被使用时(比如创建实例、访问静态成员),JVM会先加载并初始化这个类,执行顺序严格按照代码编写顺序:- 先执行静态字段的赋值操作
- 再执行静态初始化块(
static { ... })
这部分不管你后续创建多少个实例,只会跑一次。
实例创建时的初始化(每次new实例都会执行)
当你用new关键字创建实例时,会在构造方法执行前完成以下步骤:- 按代码编写顺序执行实例字段的赋值操作
- 按代码编写顺序执行实例初始化块(
{ ... }) - 最后执行构造方法中的代码
拿你提供的例子补全代码,运行后就能直观看到顺序:
public class Test { public static void main(String a[]) { Cons1 c1 = new Cons1(); } } class Cons1 { // 静态字段,类加载时初始化 static Cons1 c = new Cons1(); // 实例初始化块 { System.out.println("实例初始化块执行"); } // 静态初始化块 static { System.out.println("静态初始化块执行"); } Cons1() { System.out.println("构造方法执行"); } }
运行输出顺序为:
实例初始化块执行 构造方法执行 静态初始化块执行 实例初始化块执行 构造方法执行
拆解一下原因:
- 执行
main时第一次用到Cons1,触发类加载:- 先初始化静态字段
c = new Cons1(),此时要创建实例,进入实例初始化流程:执行实例初始化块 → 构造方法,输出前两行。 - 类加载阶段的静态字段处理完后,执行静态初始化块,输出第三行。
- 先初始化静态字段
- 类加载完成后,执行
new Cons1(),再次进入实例初始化流程:实例初始化块 → 构造方法,输出最后两行。
二、为什么加static就不会递归,不加就会?
这个问题的核心,在于静态成员和实例成员的生命周期、初始化机制完全不同:
1. 不加static的情况(实例字段)
如果把static去掉,字段c变成实例成员:
class Cons1 { Cons1 c = new Cons1(); // 实例字段 Cons1() {} }
当你执行new Cons1()时:
- 第一步要初始化当前实例的字段
c,于是调用new Cons1()创建新实例 - 这个新实例在初始化时,又要初始化它自己的实例字段
c,再次触发new Cons1() - 无限循环下去,直到JVM抛出
StackOverflowError
简单说:每个实例创建时都会初始化自己的实例字段,而实例字段的自调用构造会无限触发新实例创建,形成死递归。
2. 加static的情况(静态字段)
静态字段属于类本身,而非某个具体实例,JVM对类初始化有个关键规则:
类初始化过程是线程安全的,且同一个类只会被初始化一次。
当类加载时初始化静态字段c = new Cons1():
- 此时
Cons1类正处于「初始化中」的状态,JVM会记录这个状态,不会重复触发类初始化 - 创建实例时,虽然调用了构造方法,但构造方法不会再次触发类的静态初始化流程
- 静态字段
c只会被赋值一次,类初始化完成后,后续创建实例不会再碰这个静态字段
所以整个流程是:类加载→初始化静态字段(仅创建一次实例)→类初始化完成→后续创建实例不会再触发静态字段的初始化,自然不会递归。
总结一下:静态上下文(类层面)的初始化是一次性的,JVM有机制防止重复初始化;而非静态上下文(实例层面)的初始化是每个实例都要执行的,实例字段的自调用构造会无限触发新实例创建,最终导致递归溢出。
内容的提问来源于stack exchange,提问作者Mayank Madhav
相关产品推荐
相关产品推荐

