在Java里什么是类初始化_Java静态初始化过程说明

类初始化触发时机包括:new实例、读写非final静态字段、调用静态方法、反射加载、启动主类;父类初始化优先于子类,且静态内容严格按继承链和源码书写顺序执行。

Java里类初始化,指的是JVM首次主动使用一个类时,执行其静态成员(静态变量赋值、静态代码块)的过程;它只发生一次,且严格按“父类优先、书写顺序”执行。

类初始化触发时机有哪些

不是一加载就初始化,而是等到真正“用到”才触发。常见场景包括:

  • 执行 new 创建该类实例
  • 读取或设置非 final 静态字段(如 MyClass.count),但访问 public static final int VERSION = 1; 不会触发
  • 调用该类的静态方法(如 MyClass.doWork()
  • 通过反射(如 Class.forName("MyClass"))主动加载
  • 虚拟机启动时指定的主类(含 main 方法的类)

注意:子类初始化前,JVM会自动检查并先完成其父类的初始化——哪怕你只写 new Child()Parent 的静态部分也早已跑完。

静态初始化顺序怎么排

静态内容执行顺序由两层规则决定:继承链顺序 + 类内书写顺序。具体是:

  • 先父类所有静态变量声明与赋值(按代码顺序)→ 父类所有静态块(按出现顺序)
  • 再子类所有静态变量声明与赋值(按代码顺序)→ 子类所有静态块(按出现顺序)
  • 每个静态变量的初始化表达式(比如调用方法)会在其声明位置立即执行,不是等所有变量声明完再统一赋值

看这个例子:

class Parent {
    static int a = init("a");
    static { System.out.println("Parent static block"); }
    static int b = init("b");
    static int init(String s) { System.out.println("init " + s); return 1; }
}

class Child extends Parent {
    static int c = init("c");
    static { System.out.println("Child static block"); }
}

public class Test {
    public static void main(String[] args) {
        new Child();
    }
}

输出是:
init a
Parent static block
init b
init c
Child static block
——完全按源码中出现的位置线性执行,没有“先全声明再全赋值”这种中间状态。

为什么构造器里调用重写方法很危险

这不是静态初始化的问题,但常因混淆初始化阶段而踩坑:在父类构造器中调用一个被子类重写的方法(比如 init()),此时子类的实例变量和实例块尚未执行,但子类方法却已运行,导致访问到未初始化的字段(值为 null0)。

例如:

class Parent {
    Parent() { init(); } // 构造器中调用
    void init() { System.out.println("Parent init"); }
}

class Child extends Parent {
    String name = "child"; // 实例变量
    void init() { System.out.println("Child init: " + name); } // 重写
}

执行 new Child() 时,name 还是 null,输出为:Child init: null。问题根源在于:实例初始化(变量赋值、实例块)发生在父类构造器执行之后

、子类构造器执行之前,而父类构造器中已提前调用了子类方法。

真正容易被忽略的是:静态初始化虽只一次,但若其中抛出异常(比如 static { throw new RuntimeException(); }),整个类会进入“初始化失败”状态,后续任何对该类的主动使用都会直接抛出 ExceptionInInitializerError,且无法重试——连 Class.forName 都会失败。这类错误往往藏得深,日志里只显示顶层异常,需往堆栈深处找根本原因。