继承中类的初始化加载
在许多传统语言中,程序在启动时一次性全部加载。接着初始化,然后程序开始运行。必须仔细控制这些语言的初始化过程,以确保 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 构造器中的第一步操作)。基类构造器和派生类构造器一样以相同的顺序经历相同的过程。当基类构造器完成后,实例变量按文本顺序初始化。最终,构造器的剩余部分被执行。
步骤解读:
- 第一步先加载 Beetle 的父类 Insect ,必然要加载其中被 static 修饰的变量 x1 ,容易得出第一句输出为
static Insect.x1 initialized
。 - 第二步加载 Beetle 中的静态变量 x2 ,也能得出第二句输出为
static Beetle.x2 initialized
。 - 第三步,执行 main 方法中的语句,输出第三句为
Beetle constructor
- 第四步,执行
Beetle b = new Beetle();
语句,因为 **Beetle **继承于 Insect ,所以在执行 **Beetle ** 的构造函数之前,需要执行 Insect 的构造函数,得到输出结果i = 9, j = 0
,注意,这里的 j 为 0 ,因为在这之前,j 并没有进行赋值初始化,所以为默认值 0 。 - 第五步,加载 Beetle 中的成员变量 k ,输出
Beetle.k initialized
。 - 第六步,调用 **Beetle **的构造函数,输出
k = 47
和j = 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