揭秘Java对象的执行顺序

141 阅读5分钟

知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方可以评论,我们一起探讨!

类加载阶段

Java 程序在运行时,JVM 会将类加载到内存中,类加载过程包括加载、验证、准备、解析和初始化五个阶段,其中与执行顺序密切相关的是准备和初始化阶段。

  • 准备阶段:为类的静态变量分配内存,并将其初始化为默认值。例如,int 类型的静态变量初始化为 0,boolean 类型的初始化为 false,引用类型初始化为 null 等。
  • 初始化阶段:执行类的静态代码块和静态变量的显式赋值。静态代码块和静态变量的初始化顺序是按照它们在类中出现的先后顺序执行的。

举例代码及分析

public class ClassExecutionOrder {
    // 静态变量
    public static int staticVariable = 1;
    // 静态代码块
    static {
        System.out.println("静态代码块执行,staticVariable 的值为: " + staticVariable);
        staticVariable = 2;
    }
    // 实例变量
    public int instanceVariable = 3;
    // 实例代码块
    {
        System.out.println("实例代码块执行,instanceVariable 的值为: " + instanceVariable);
        instanceVariable = 4;
    }
    // 构造方法
    public ClassExecutionOrder() {
        System.out.println("构造方法执行,instanceVariable 的值为: " + instanceVariable);
    }

    public static void main(String[] args) {
        System.out.println("main 方法开始");
        // 创建对象
        ClassExecutionOrder obj = new ClassExecutionOrder();
        System.out.println("main 方法结束");
    }
}

执行顺序详细分析

  1. 静态成员初始化

    • 当 JVM 启动并执行 main 方法时,首先会加载 ClassExecutionOrder 类。在类的加载过程中,会先执行准备阶段,为静态变量 staticVariable 分配内存并初始化为默认值 0。
    • 接着进入初始化阶段,按照静态成员在类中出现的顺序执行。首先对 staticVariable 进行显式赋值为 1,然后执行静态代码块,输出 静态代码块执行,staticVariable 的值为: 1,并将 staticVariable 的值修改为 2。
  2. main 方法执行:输出 main 方法开始

  3. 实例成员初始化

    • 当执行 new ClassExecutionOrder() 时,会创建 ClassExecutionOrder 类的一个实例对象。在创建对象之前,会先为实例变量分配内存并初始化为默认值。这里 instanceVariable 被初始化为 0。
    • 然后按照实例成员在类中出现的顺序执行。首先执行实例代码块,输出 实例代码块执行,instanceVariable 的值为: 3,并将 instanceVariable 的值修改为 4。
    • 最后执行构造方法,输出 构造方法执行,instanceVariable 的值为: 4
  4. main 方法结束:输出 main 方法结束

存在继承关系时的执行顺序

当存在继承关系时,执行顺序会更加复杂。以下是一个示例代码:

class Parent {
    public static int parentStaticVariable = 1;
    static {
        System.out.println("父类静态代码块执行,parentStaticVariable 的值为: " + parentStaticVariable);
        parentStaticVariable = 2;
    }
    public int parentInstanceVariable = 3;
    {
        System.out.println("父类实例代码块执行,parentInstanceVariable 的值为: " + parentInstanceVariable);
        parentInstanceVariable = 4;
    }
    public Parent() {
        System.out.println("父类构造方法执行,parentInstanceVariable 的值为: " + parentInstanceVariable);
    }
}

class Child extends Parent {
    public static int childStaticVariable = 5;
    static {
        System.out.println("子类静态代码块执行,childStaticVariable 的值为: " + childStaticVariable);
        childStaticVariable = 6;
    }
    public int childInstanceVariable = 7;
    {
        System.out.println("子类实例代码块执行,childInstanceVariable 的值为: " + childInstanceVariable);
        childInstanceVariable = 8;
    }
    public Child() {
        System.out.println("子类构造方法执行,childInstanceVariable 的值为: " + childInstanceVariable);
    }

    public static void main(String[] args) {
        System.out.println("main 方法开始");
        Child child = new Child();
        System.out.println("main 方法结束");
    }
}

存在继承关系时的执行顺序分析

  1. 父类静态成员初始化

    • 加载 Child 类时,由于 Child 类继承自 Parent 类,JVM 会先加载 Parent 类。在 Parent 类的准备阶段,为 parentStaticVariable 分配内存并初始化为 0。
    • 进入 Parent 类的初始化阶段,先对 parentStaticVariable 显式赋值为 1,然后执行 Parent 类的静态代码块,输出 父类静态代码块执行,parentStaticVariable 的值为: 1,并将 parentStaticVariable 的值修改为 2。
  2. 子类静态成员初始化

    • 接着对 Child 类进行准备阶段,为 childStaticVariable 分配内存并初始化为 0。
    • 进入 Child 类的初始化阶段,先对 childStaticVariable 显式赋值为 5,然后执行 Child 类的静态代码块,输出 子类静态代码块执行,childStaticVariable 的值为: 5,并将 childStaticVariable 的值修改为 6。
  3. main 方法执行:输出 main 方法开始

  4. 父类实例成员初始化

    • 创建 Child 对象时,会先调用父类的实例初始化过程。为 parentInstanceVariable 分配内存并初始化为 0。
    • 执行 Parent 类的实例代码块,输出 父类实例代码块执行,parentInstanceVariable 的值为: 3,并将 parentInstanceVariable 的值修改为 4。
    • 执行 Parent 类的构造方法,输出 父类构造方法执行,parentInstanceVariable 的值为: 4
  5. 子类实例成员初始化

    • 为 childInstanceVariable 分配内存并初始化为 0。
    • 执行 Child 类的实例代码块,输出 子类实例代码块执行,childInstanceVariable 的值为: 7,并将 childInstanceVariable 的值修改为 8。
    • 执行 Child 类的构造方法,输出 子类构造方法执行,childInstanceVariable 的值为: 8
  6. main 方法结束:输出 main 方法结束

总结

  • 无继承关系时,执行顺序为:静态变量初始化 -> 静态代码块 -> main 方法 -> 实例变量初始化 -> 实例代码块 -> 构造方法。
  • 存在继承关系时,执行顺序为:父类静态变量初始化 -> 父类静态代码块 -> 子类静态变量初始化 -> 子类静态代码块 -> main 方法 -> 父类实例变量初始化 -> 父类实例代码块 -> 父类构造方法 -> 子类实例变量初始化 -> 子类实例代码块 -> 子类构造方法。