初始化基类
在Java中,当初始化涉及到继承体系时会变得复杂一些。有继承时,现在涉及到子类与基类两个类,子类就像是一个与基类有相同接口的新类,或许还会有一些额外的方法和成员。但继承并不只是复制基类的接口,当创建一个子类的对象时,改对象包含了一个基类的子对象,这个子对象与直接用基类创建的对象是一样的。二者区别在于,后者来自于外部,而基类子对象被包装在导出类对象的内部。
对基类子对象的正确初始化也是至关重要的,而且只有一种方式来保证这一点:在构造器中调用基类的构造器来执行初始化,而基类构造器具有执行基类初始化所需要的所有知识和能力。Java会自动在导出类的构造器中插入对基类构造器的调用。
示例:
class Art{
Art(){
System.out.println("Art constructor");
}
}
class Drawing extends Art{
Drawing(){
System.out.println("Drawing constructor");
}
}
class Cartoon extends Drawing{
Cartoon(){
System.out.println("Cartoon constructor");
}
public static void main(String[] args) {
new Cartoon();
}
}
输出:
Art constructor
Drawing constructor
Cartoon constructor
从输出看到,Art类的构造器先执行了,然后是Drawing类的构造器,最后是Cartoon类的构造器被执行。构建过程是从基类向外扩散的,在导出类构造器可以访问之前,基类已经初始化完成。
有参数的构造器
如果基类中显示的定义了带参数的构造器,就不会有默认的无参数构造器了,这时就需要在子类的构造器中显示的调用基类的相应构造器,使用super关键字,配上适当的参数列表。否则编译器会报告基类的默认构造器未定义。
示例:
class Game {
Game(int i){
System.out.println("Game constructor");
}
}
class BoardGame extends Game{
BoardGame(int i){
super(i);
System.out.println("BoardGame constructor");
}
}
class Chess extends BoardGame{
Chess() {
super(11);
System.out.println("Chess constructor");
}
public static void main(String[] args) {
new Chess();
}
}
输出:
Game constructor
BoardGame constructor
Chess constructor
如果把Chess或BoardGame类构造器中的super语句注释掉,编译器会报错的。
继承与初始化
看示例: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 initilized");
static int printInit(String s) {
System.out.println(s);
return 47;
}
}
class Beetle extends Insect {
private int k = printInit("Beetle.k initilized");
public Beetle(){
System.out.println("k="+k);
System.out.println("j="+j);
}
private static int x2 = printInit("static Beetle.x2 initilized");
public static void main(String[] args) {
System.out.println("Beetle constructor");
new Beetle();
}
}
输出:
static Insect.x1 initilized
static Beetle.x2 initilized
Beetle constructor
i=9, j=0
Beetle.k initilized
k=47
j=39
解释输出:
程序运行第一件事就是访问Beetle.main方法,加载器会加载Beetle类的编译代码(在Beetle.class文件中),在加载Beetle.class时,发现它有一个基类Inset类,于是继续加载Insetc类的编译代码,不管是否打算产生一个Insect类的对象,这都要发生。如果Insect类还有它自己的基类,这个过程还要类推。
接着,根基类的static数据执行初始化,此处是Insect类中的static数据x1,初始化x1时通过调用静态方法printInit,
于是打印“static Insect.x1 initilized”。
然后是导出类Beetle的静态数据x2初始化,
打印static Beetle.x2 initilized。
类加载完毕,开始执行main方法,打印“Beetle constructor”。
接着创建Beetle对象,分配足够的存储空间,Beetle对象的各项数据清零。
然后调用构造器的时候会导致基类的构造器被调用初始化基类,这个过程会持续到根类的构造器。此处是Insect类。
对Insect类的数据成员执行初始化,使
i=9,j=0
执行Insect类的构造体部分,打印“i=9, j=0”,并使
j=39
基类初始化完毕,然后执行子类Beetle的数据成员k的初始化,通过继承的方法printInit初始化,打印“Beetle.k initilized”,然后返回一个整数47给k。
接着执行Beetle类的构造体,分别打印k和j的值:“k=47”,“j=39”
对象创建完毕。