继承中类的初始化加载

继承中类的初始化加载

本篇博客示例来自来自 https://github.com/BruceEckel/OnJava8-examples

且大多数内容引用 https://lingcoder.github.io/OnJava8/#/sidebar

在许多传统语言中,程序在启动时一次性全部加载。接着初始化,然后程序开始运行。必须仔细控制这些语言的初始化过程,以确保 statics 初始化的顺序不会造成麻烦。在 C++ 中,如果一个 static 期望使用另一个 static,而另一个 static 还没有初始化,就会出现问题。

Java 中不存在这样的问题,因为它采用了一种不同的方式加载。因为 Java 中万物皆对象,所以加载活动就容易得多。记住每个类的编译代码都存在于它自己独立的文件中。该文件只有在使用程序代码时才会被加载。一般可以说“类的代码在首次使用时加载“。这通常是指创建类的第一个对象,或者是访问了类的 static 属性或方法。构造器也是一个 static 方法尽管它的 static 关键字是隐式的。因此,准确地说,一个类当它任意一个 static 成员被访问时,就会被加载。

首次使用时就是 static 初始化发生时。所有的 static 对象和 static 代码块在加载时按照文本的顺序(在类中定义的顺序)依次初始化。static 变量只被初始化一次。

分析以下示例;

class Insect {
    private int i = 9;
    protected int j;
    
    Insect() {
        System.out.println("i = " + i + ", j = " + j);
        j = 39;
    }
    
    private static int x1 = printInit("static Insect.x1 initialized");
    
    static int printInit(String s) {
        System.out.println(s);
        return 47;
    }
}

public class Beetle extends Insect {
    private int k = printInit("Beetle.k.initialized");
    
    public Beetle() {
        System.out.println("k = " + k);
        System.out.println("j = " + j);
    }
    
    private static int x2 = printInit("static Beetle.x2 initialized");
    
    public static void main(String[] args) {
        System.out.println("Beetle constructor");
        Beetle b = new Beetle();
    }
}

输出结果:

static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39

当执行 java Beetle,首先会试图访问 Beetle 类的 main() 方法(一个静态方法),加载器启动并找出 Beetle 类的编译代码(在名为 Beetle.class 的文件中)。在加载过程中,编译器注意到有一个基类,于是继续加载基类。不论是否创建了基类的对象,基类都会被加载。(可以尝试把创建基类对象的代码注释掉证明这点。)

如果基类还存在自身的基类,那么第二个基类也将被加载,以此类推。接下来,根基类(例子中根基类是 Insect)的 static 的初始化开始执行,接着是派生类,以此类推。这点很重要,因为派生类中 static 的初始化可能依赖基类成员是否被正确地初始化。

至此,必要的类都加载完毕,对象可以被创建了。首先,对象中的所有基本类型变量都被置为默认值,对象引用被设为 null —— 这是通过将对象内存设为二进制零值一举生成的。接着会调用基类的构造器。本例中是自动调用的,但是你也可以使用 super 调用指定的基类构造器(在 Beetle 构造器中的第一步操作)。基类构造器和派生类构造器一样以相同的顺序经历相同的过程。当基类构造器完成后,实例变量按文本顺序初始化。最终,构造器的剩余部分被执行。

步骤解读:

  1. 第一步先加载 Beetle 的父类 Insect ,必然要加载其中被 static 修饰的变量 x1 ,容易得出第一句输出为 static Insect.x1 initialized
  2. 第二步加载 Beetle 中的静态变量 x2 ,也能得出第二句输出为 static Beetle.x2 initialized
  3. 第三步,执行 main 方法中的语句,输出第三句为 Beetle constructor
  4. 第四步,执行 Beetle b = new Beetle();语句,因为 **Beetle **继承于 Insect ,所以在执行 **Beetle ** 的构造函数之前,需要执行 Insect 的构造函数,得到输出结果 i = 9, j = 0,注意,这里的 j 为 0 ,因为在这之前,j 并没有进行赋值初始化,所以为默认值 0 。
  5. 第五步,加载 Beetle 中的成员变量 k ,输出 Beetle.k initialized
  6. 第六步,调用 **Beetle **的构造函数,输出 k = 47j = 39

思考:

如果在 Insect 的成员变量中加入一句 private int test = printInit("static Insect.test initialized");,是否输出结果会发生变化,如果发生变化,又会在哪里进行输出?

首先咱们要知道的是,成员变量初始化会在构造函数之前进行,与静态变量不同的是,静态变量会在当前类被加载时进行初始化,而成员变量是在创建对象时进行初始化。

所以输出结果为:

static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
static Insect.test initialized
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39

继承中类的初始化加载

上一篇:Excel如何来绘制不同函数的图像


下一篇:mybatis 第一个实例