Java基础巩固之类的初始化顺序

在java中,通常一个类的组成部分分为:属性(变量,常量)、构造器、代码块和方法。这些组成都分为两种:静态成员(依赖于类)和非静态成员(依赖于对象)

而在一个类的初始化过程中,对于静态变量、静态代码块、成员变量、构造代码块、构造器,它们的初始化顺序依次是:

(静态变量、静态代码块)>(成员变量、构造代码块)>构造器

我们也可以通过下面的测试代码来验证这一点:

public class InitialOrderTest {
    // 静态变量
    public static int staticField = varInit("静态变量开始被显示初始化!");
    // 成员变量
    public int field = varInit("成员变量开始被显示初始化!");
    // 静态代码块
    static {
        System.out.println("静态代码块执行");
        System.out.println("静态变量staticField的值:"+staticField);
    }
    // 构造代码块
    {
        System.out.println("构造代码块执行");
        System.out.println("成员变量field的值:"+field);
    }

    // 构造器
    public InitialOrderTest() {
        System.out.println("构造器执行");
    }

    public static int varInit(String msg) {
        System.out.println(msg);
        return 888;
    }

    public static void main(String[] args) {
        System.out.println("main方法开始执行");
        new InitialOrderTest();
    }
}



运行该程序的输出结果为:

静态变量开始被显示初始化!
静态代码块执行
静态变量staticField的值:888
main方法开始执行
成员变量开始被显示初始化!
构造代码块执行
成员变量field的值:888
构造器执行

从打印的结果看到,与(静态变量、静态代码块)>(成员变量、构造代码块)> 构造器的规律完全符合。那么在类的初始化过程中,内存中究竟是在执行怎么样的过程呢?

当我们开始运行InitialOrderTest类:

(1)、进行类的加载工作:

首先虚拟机会在ClassPath对应的路径下查找到InitialOrderTest.class文件,随后类装载器(classLoader)会将该类加载到内存当中。此时,类的成员变量,成员方法,构造器等都将被加载至内存中的方法区当中;而该类中的静态变量与静态方法将被加载进方法区中一块特殊的区域:静态区。

(2)、进行类的静态初始化工作:

当类被加载到内存中之后,接下来便会按照静态成员的代码先后顺序,进行该类的静态初始化工作。

上面的例子中静态变量staticField的定义,写在了静态代码块之前,所以首先会对该类静态成员staticField进行初始化工作(你可以修改它们之间的顺序看看控制台的输出信息有何不同),所以输出信息首先打印了:静态变量开始被显示初始化!

接下来,当类的静态成员的初始化工作完成后,便会执行静态代码块当中的内容,于是接着打印了:静态代码块执行、静态变量staticField的值:888


这里值得注意的是,在上面的程序中,我们利用的是InitialOrderTest类当中的静态方法varInit来对静态变量staticField进行显示初始化赋值工作。这也恰恰印证了:在类的一切初始化工作开始之前,JVM首先会完成类的装载工作。因为正是因为此时InitialOrderTest类已经被成功加载到了内存之中,所以这里在进行静态变量staticField的初始化时,我们才能成功调用到varInit方法。如果类的加载工作在之后完成,那么这里我们根本调用不到varInit方法。

(3)、进行类的成员初始化工作:

当完成上面2步工作之后,main才会被压栈,真正开始执行,所以打印信息:main方法开始执行构造代码块执行、成员变量field的值:888

接下来的代码new InitialOrderTest()意味着要创建一个InitialOrderTest类的对象,所以虚拟机会首先在堆内存中开辟一块空间存放该对象。接下来则开始执行该对象的初始化工作。

首先会开始对该类的成员变量进行显式初始化工作(堆内存中存储的对象的成员最初会有一个默认初始化值),所以接着打印的信息为:成员变量开始被显示初始化!

接着,当该类对象中的成员变量初始化完成后,就会运行构造代码块中的内容,于是打印信息:构造代码块执行、成员变量field的值:888

(4)、执行类的构造器

当完成该类对象的成员初始化工作后,最后,才会执行该类构造器中的内容完成创建的该类对象的所有初始化工作。所以,最后打印的信息为:构造器执行


最后,值得我们注意并牢记的是:上述过程中的第(1)、(2)步初始化工作,是运行一个类时,必然会执行的工作。不管你创不创建该类的对象,JVM都会完成对该类的加载与静态初始化(很好理解,Java中一个类的静态成员并不依赖于对象,而是依赖于类,所以当要使用该类的静态成员时,必须保证该类被正确加载到了内存,并且静态成员都得到了正确的初始化,否则,如何保证我们能稳妥的使用类名去调用一个静态成员呢?就例如我们在上面例子中的main方法长访问静态变量staticField,如果在此之前没有完成类的装载工作,那么我们将访问不到staticField(因为它根本不存在于内存中!);如果完成了加载,但是没有进行静态初始化,那么我们虽然可以访问到staticField,但得到的却不是正确的值)。当然你可以修改上面的程序进行验证,注释掉main方法中的第二行代码:new InitialOrderTest();,你会发现运行该类的打印结果为:

静态变量开始被显示初始化!
静态代码块执行
静态变量staticField的值:888
main方法开始执行


到此,我们应该已经很清楚的了解了,java中一个类的初始化工作顺序为:类装载工作→静态初始化→成员初始化→构造器。

但是,还需要我们注意的是,如果当类出现了继承关系时,父类的初始化工作会被放在首位(子依赖于父存在,自然应当先有父才有子)。例如有一个类B,继承了类A。此时的初始化过程可以归纳为:

1、找到B.class进行装载,在装载的过程中,装载器发现该类还有一个父类A,于是又找到A.class进行装载

2、当类的装载都完成后,首先会完成父类(A)的静态初始化,接着,会进行子类(B)的静态初始化。

3、当静态初始化完成后,首先会进行父类(A)的成员(非静态)初始化:包括A中的成员变量初始化以及执行A的构造代码块。完成以上工作后,接下来会执行A的构造器。

4、当执行完父类(A)的构造器后,才接着进行子类(B)的成员(非静态)初始化。最后,执行B的构造器完成整个初始化过程。


你可能感兴趣的:(java,继承,ClassLoader,内存,java类初始化顺序)