Java中各成员变量初始化流程

0.前言

最近在看Thinking in java,关于类中的各成员初始化一直未曾关注,这里记录一下,如有错误,请指正:

1.继承与初始化

了解包括继承在内的初始化全过程,以对所发生的一切有个全局性的把握,是很有益的。

在Java中,每个类的编译代码都存在于它自己的独立的文件中(即class文件),该文件只在需要使用程序代码时才会加载。一般来说,“类的代码在初次使用的时候才加载”,这通常包含下面两种情况:

  1. 创建类的第一个对象的时候
  2. 访问static方法或字段时

注意:初次使用类时,也是static初始化发生之处,所有对象和代码均按照书写顺序进行初始化,定义为static的字段仅会被初始化一次。

废话不多说,详细如下:

public class Base {
    //static字段
    private static int I1 = printInt("Init I1");
    //普通字段
    private int i = 11;
    protected int j;
    public Base(){
        System.out.println("Base constructor");
        System.out.println("i="+i+" j="+j);
        j = 40;
    }
    static int printInt(String str) {
        System.out.println(str);
        return 10;
    }
}
public class Son extends Base{
    private static int I2 = printInt("Init I2");
    public Son() {
        System.out.println("Son constructor");
        System.out.println("I2="+I2+" j= "+j);
    }
    public static void main(String[] args) {
        Son son = new Son();
    }
}

看完这段代码,你觉得输出是什么呢?

先给出答案,在来分析:

Init I1
Init I2
Base constructor
i=11 j=0
Son constructor
I2=10 j= 40

分析:

程序首先从Son类的main方法入手,于是加载器开始启动并找出Son编译代码(即class文件)。在进行加载中发现Son还有父类Base,于是继续加载Base的编译代码(如果Base还有父类则继续向上执行),接下来,根基类的static字段初始化,因为子类可能会依赖基类成员能否被正确初始化,所以发生了Init I1, 然后往下到了Son,Son类的static字段初始化,于是发生了Init I2 ,到此所有的必要的类加载完成,可以开始初始化对象。看到Son son = new Son()这行代码,准备调用Son的构造器,我们知道在继承关系中,子类的构造器中会调用super(),当然这里是隐式调用。这样又会回到父类中去,不过在完成构造器之前,父类(Base)中的所有普通字段(即非static字段)都会完成自己的初始化,所以会看到输出i=11 j=0,接着来到子类(Son),和父类的执行过程一样,先完成普通字段的初始化,再调用构造器方法。

说了一大堆,整体流程如下:

  1. 从程序入口开始,加载该类(这里设为Z类),如有继承关系递归向上,直到根类(这里假设A类)。
  2. 完成A类的static字段初始化,递归向下,直到遇到Z类。
  3. 完成A类的普通域初始化,完成A类的构造器,递归向下,直到遇到Z类。

你可能感兴趣的:(Java)