Java类中对象域的初始化

java中,一个类的域(或者说类的属性,类的数据成员)可以是基本数据类型,也可以是对象的引用。对象域如果没有被初始化,会被置为null,如果在置为null的引用上调用方法,我们就会得到一个运行时错误。
在恰当的时间,恰当的位置,正确地初始化对象域是很重要的。

java中可以在四个位置进行类的对象域初始化:

  • 在域定义时初始化
  • 在类构造器中初始化
  • 在使用类中的对象域之前手动初始化(这被称作懒惰初始化
  • 使用实例初始化块(常用于匿名内部类的初始化)

要注意上面列出的顺序并不是这些位置初始化的时间顺序。我们经常能看到的初始化方式是1和2。

当然,我们不能忘了一个特殊的存在:静态成员。
如果一个对象域是静态的,那么它的初始化就和类对象的初始化剥离开了。
静态成员的初始化在类被加载时进行,这是我们一定要知道的,但不是这篇文章的重点。

下面通过代码来证明这四种初始化发生的时间顺序。
类A拥有一个静态数据成员sstrA,和一个数据成员strA。拥有一个静态初始化块,和一个类初始化块。有一个无参构造函数。override了toString方法,在toString方法中懒惰初始化strA。
而类B结构与A基本相同,并且继承自A。

class A {
    static String sstrA = "static string in A";
    String strA = "string in A";  //在定义时初始化

    static {  //静态初始化块
        System.out.println("A static init block start");
        sstrA = "sstrA from init block";
    }

    {  //实例初始化块
        System.out.println("instance init block start");
        strA = "strA from init block";
    }

    public A() {  //构造函数中可以做初始化
        System.out.println("constructor start");
    }

    @Override
    public String toString() {
        if (strA == null) //这里使用了懒惰初始化
            strA = "strA from delayed init";
        return "static sstrA: " + sstrA + "\nstrA: " + strA;
    }
}

class B extends A {
    static String sstrB = "static string in B";
    String strB = "string in B";

    static {
        System.out.println("B static init block start");
        sstrB = "sstrB from init block";
    }

    {
        System.out.println("instance init block start");
        strB = "strB from init block";
    }

    public B() {
        System.out.println("constructor start");
    }

    @Override
    public String toString() {
        if (strB == null)
            strB = "strB from delayed init";
        return super.toString() + "\nstatic sstrB: " + sstrB + "\nstrA: " + strB;
    }
}

在main函数中创建这两个类的实例。

        A a = new A();
        System.out.println("----------------");
        System.out.println(a);

        System.out.println("================");

        B b = new B();
        System.out.println("----------------");
        System.out.println(b);
A static init block start
A instance init block start
A constructor start
----------------
static sstrA: sstrA from init block
strA: strA from init block
================
B static init block start
A instance init block start
A constructor start
B instance init block start
B constructor start
----------------
static sstrA: sstrA from init block
strA: strA from init block
static sstrB: sstrB from init block
strA: strB from init block

根据前半部分的运行结果,我们看到首先在类加载时,静态初始化块被执行了。
然后便是类实例化创建对象的过程,在构造函数被调用前,实例初始化块被首先执行。
根据toString的返回字符串,我们看到显示的内容是实例初始化块中赋值的字符串,说明最一开始在定义处初始化的字符串被实例初始化块覆盖掉。
最终得出一个顺序:

  • 在最一开始,类被加载时,静态相关的初始化会执行
  • 类实例化的第一步,定义处的初始化会被执行
  • 然后是实例初始化块的执行
  • 再后面才是构造器
  • 最后当然是懒惰初始化在必要时得到执行

然后后半部分B类的实例化过程展现了继承存在时初始化的顺序。

因为A类在之前已经被加载过,所以静态初始化块不会被执行,此处只有B类的静态初始化块。

在实例化过程中首先会进行父类A的初始化,然后才是B本身的初始化。

总的来说,java在类成员初始化的顺序上,并没有什么反直觉的地方,只要稍加理解便能掌握。

posted @ 2018-08-07 15:37  Redotz  阅读(1225)  评论(0编辑  收藏  举报