5、一个类中的各个元素的加载、初始化顺序是怎样的?

#【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道!#

在 Java 中,一个类的加载、链接、初始化过程由 JVM 严格按照规范执行,而类内部各元素(静态成员、实例成员、构造器等)的初始化顺序也有明确规则。理解这一顺序对掌握对象创建机制、避免初始化错误(如空指针、未定义行为)至关重要。


一、整体流程:类的生命周期(JVM 层面)

Java 类的生命周期分为以下阶段:

  1. 加载(Loading)

    • 通过类加载器(ClassLoader)将 .class 字节码文件读入内存;
    • 生成 java.lang.Class 对象。
  2. 链接(Linking)

    • 验证(Verification):确保字节码安全、合法;
    • 准备(Preparation):为静态变量分配内存并设置默认初始值(如 0nullfalse),不执行初始化代码
    • 解析(Resolution):将符号引用转为直接引用(可选,部分在初始化后进行)。
  3. 初始化(Initialization)

    • 执行静态初始化代码:包括静态变量显式赋值和静态代码块(static {});
    • 这是类初始化阶段,也是我们关注“初始化顺序”的核心。

触发类初始化的时机(JLS §12.4.1):

  • 创建类的实例(new);
  • 访问类的静态非常量字段(static final 基本类型/字符串字面量除外);
  • 调用类的静态方法;
  • 使用反射(如 Class.forName());
  • 初始化子类时,先初始化父类;
  • 启动类(含 main 方法的类)。

二、类内部元素的初始化顺序(重点)

当一个类被初始化(或创建对象)时,其内部元素按以下严格顺序执行:

📌 1. 父类优先原则

先初始化父类,再初始化子类(递归至 Object)。

📌 2. 静态成员先于实例成员

静态内容只初始化一次(类加载时),实例内容每次创建对象时初始化。

📌 3. 同类中按代码书写顺序执行


✅ 完整初始化顺序(创建对象时)

假设有一个子类 Child 继承自 Parent,两者均包含各类成员:

1. 父类 Parent 的静态变量(默认值 → 显式赋值)
2. 父类 Parent 的静态代码块(static {})
3. 子类 Child 的静态变量(默认值 → 显式赋值)
4. 子类 Child 的静态代码块(static {})
   ↓
5. 父类 Parent 的实例变量(默认值 → 显式赋值)
6. 父类 Parent 的实例代码块({})
7. 父类 Parent 的构造器
8. 子类 Child 的实例变量(默认值 → 显式赋值)
9. 子类 Child 的实例代码块({})
10. 子类 Child 的构造器

💡 关键点

  • 静态内容在类首次主动使用时初始化一次;
  • 实例内容在每次 new 对象时执行;
  • 变量赋值与代码块按源码从上到下顺序执行。

三、详细示例与中文注释说明

示例代码:展示完整初始化顺序

// 父类
class Parent {
    // 1. 静态变量(准备阶段设为 null,初始化阶段赋值)
    static String parentStaticField = initParentStaticField();

    // 2. 静态代码块
    static {
        System.out.println("【父类】静态代码块执行");
    }

    // 5. 实例变量
    String parentInstanceField = initParentInstanceField();

    // 6. 实例代码块
    {
        System.out.println("【父类】实例代码块执行");
    }

    // 7. 构造器
    public Parent() {
        System.out.println("【父类】构造器执行");
    }

    // 辅助方法(用于观察初始化时机)
    static String initParentStaticField() {
        System.out.println("【父类】静态变量初始化");
        return "Parent Static";
    }

    String initParentInstanceField() {
        System.out.println("【父类】实例变量初始化");
        return "Parent Instance";
    }
}

// 子类
class Child extends Parent {
    // 3. 静态变量
    static String childStaticField = initChildStaticField();

    // 4. 静态代码块
    static {
        System.out.println("【子类】静态代码块执行");
    }

    // 8. 实例变量
    String childInstanceField = initChildInstanceField();

    // 9. 实例代码块
    {
        System.out.println("【子类】实例代码块执行");
    }

    // 10. 构造器
    public Child() {
        System.out.println("【子类】构造器执行");
    }

    static String initChildStaticField() {
        System.out.println("【子类】静态变量初始化");
        return "Child Static";
    }

    String initChildInstanceField() {
        System.out.println("【子类】实例变量初始化");
        return "Child Instance";
    }
}

// 测试类
public class InitializationOrderDemo {
    public static void main(String[] args) {
        System.out.println("=== 第一次创建 Child 对象 ===");
        new Child();

        System.out.println("\n=== 第二次创建 Child 对象 ===");
        new Child(); // 静态部分不会重复执行
    }
}

🔍 输出结果分析:

=== 第一次创建 Child 对象 ===
【父类】静态变量初始化
【父类】静态代码块执行
【子类】静态变量初始化
【子类】静态代码块执行
【父类】实例变量初始化
【父类】实例代码块执行
【父类】构造器执行
【子类】实例变量初始化
【子类】实例代码块执行
【子类】构造器执行

=== 第二次创建 Child 对象 ===
【父类】实例变量初始化
【父类】实例代码块执行
【父类】构造器执行
【子类】实例变量初始化
【子类】实例代码块执行
【子类】构造器执行

结论

  • 静态部分(1-4)仅在首次使用类时执行一次
  • 实例部分(5-10)每次 new 对象都执行
  • 父类始终先于子类初始化。

四、特殊情况说明

4.1 静态常量(static final)不触发类初始化

class Constants {
    public static final int VALUE = 100; // 编译期常量
    static {
        System.out.println("Constants 类被初始化!");
    }
}

public class ConstantDemo {
    public static void main(String[] args) {
        System.out.println(Constants.VALUE); // 直接输出 100,不触发初始化!
    }
}

输出:100(无“Constants 类被初始化!”)
原因:VALUE 是编译期常量,值已内联到调用处,无需加载 Constants 类。

4.2 静态变量赋值 vs 静态代码块

class OrderTest {
    static int a = 1;
    static {
        a = 2;
    }
    static int b = a; // b = 2

    public static void main(String[] args) {
        System.out.println("a=" + a + ", b=" + b); // a=2, b=2
    }
}

说明:静态变量和静态代码块按源码顺序执行

4.3 构造器中的 super() 调用

  • 子类构造器第一行隐式或显式调用 super()
  • 因此父类构造器总是在子类构造器之前执行。

五、总结:初始化顺序口诀

“父静子静,父实子实;
变量代码块,顺序不能乱;
构造最后来,super 在最前。”

阶段元素执行时机次数
静态初始化父类静态变量/代码块类首次主动使用时1 次
子类静态变量/代码块同上1 次
实例初始化父类实例变量/代码块每次 new 对象时每次
父类构造器同上每次
子类实例变量/代码块同上每次
子类构造器同上每次

六、实际应用建议

  1. 避免在静态初始化中依赖未初始化的静态变量
  2. 不要在父类构造器中调用可能被子类重写的方法(此时子类尚未初始化,可能导致空指针);
  3. 使用 final 字段确保初始化完成后不可变
  4. 理解类初始化时机,避免 ClassNotFoundExceptionNoClassDefFoundError

掌握类初始化顺序,是深入理解 Java 对象模型、JVM 机制和编写健壮代码的基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值