了解包括继承在内的初始化全过程,对所发生的一切有个全局性的把握,是很有益的。
//: Beetle.java
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 + "\nj = " + j);
}
private static int x2 = printInit("static Beetle.x2 initialized");
public static void main(String[] args) {
System.out.println("Beetle constructor");
Beetle beetle = new Beetle();
}
}
output:
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9,j = 0
Beetle.k initialized
k = 47
j = 39
在Beetle上运行java时,所发生的第一件事是试图访问Beetle.main() (main是一个static方法),于是加载器开始启动并找出Beetle类的编译代码(在Beetle.class文件中)。在对它加载的过程中,编译器注意到它有一个基类,于是它继续进行加载。不管是否打算产生一个该基类的对象,这都要发生。
验证上一句话,将对象创建代码注释后 //Beetle beetle = new Beetle();
output:
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
如果该基类还有自身的基类,那么第二个基类就会加载,如此类推。接下来,根基类的static初始化就会被执行,然后是下一个派生类,以此类推。这种方式很重要,因为派生类的static初始化可能会依赖积累成员能否被正确初始化。
至此为止,必要的类都已经加载完毕,对象就可以创建了。首先,对象中所有的基本类型都会被设为默认值,对象引用被设为null。然后,基类的构造器就会被调用。在基类构造器完成之后,实例变量按次序被初始化。最后,构造器的其余部分被执行。
上例中main方法位于Beetle中,接下来测试另一种情况:
//: Test.java
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;
}
}
class Beetle extends Insect {
private int k = printInit("Beetle.k initialized");
public Beetle() {
System.out.println("k = " + k + "\nj = " + j);
}
private static int x2 = printInit("static Beetle.x2 initialized");
}
public class Test {
public static void main(String[] args) {
System.out.println("Beetle constructor");
Beetle beetle = new Beetle();
}
}
output:
Beetle constructor
static Insect.x1 initialized
static Beetle.x2 initialized
i = 9,j = 0
Beetle.k initialized
k = 47
j = 39
从此处可以发现,main方法中第一行的打印语句先执行了。
接下来把对象创建语句注释掉 //Beetle beetle = new Beetle();
output:
Beetle constructor
可以看到只有一条打印语句。此例可以帮助更清晰的了解类加载机制的第一步,对main方法的访问。
参考资料:thinking in java