java继承中类的初始化过程

时间:2022-03-04 19:42:19

我们先看代码,然后再分析。

package se;

public class Beetle extends Insect{
int k = prt("Beetle.k initialized");
Beetle(){
prt("k="+k);
prt("j="+j);
}
static int x2=prt("static Beetle.x2 initialized");
static{
System.out.println("Beetle");
}
static int prt(String s){
System.out.println(s);
return 63;
}
public static void main(String[] args) {
prt("Beetle constructor");
Beetle b = new Beetle();
}
}
class Insect{
int i=9;
int j;
Insect(){
j=prt("i="+i+",j="+j);
}
static{
System.out.println("Insect");
}
static int xl = prt("static Insect.x1 initialized");
static int prt(String s){
System.out.println(s);
return 47;
}
}

输出:
Insect
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle
Beetle constructor
i=9,j=0
Beetle.k initialized
k=63
j=47

java中类的class文件只有在初次使用时才会加载到jvm中。类的静态成员、静态代码块和静态方法会随着类的加载而加载,并且只加载一次。初次使用包含两种情况:

  1. 静态调用。就是“类名.静态成员”或“类名.静态方法”。

  2. 使用构造器。

上面的程序先从Beetle类的main方法形如执行(可以理解成jvm执行Beetle.main(),也就是静态调用),于是类加载器开始加载Beetle类的字节码文件。在此过程中,编译器发现了“extends”关键字,即Beetle类还继承了Insect类,于是接着加载Insect类。上面的例子包含静态初始化和对象初始化两个过程。首先是开始Insect类(父类)的静态初始化。

static{
System.out.println("Insect");
}
static int xl = prt("static Insect.x1 initialized");

静态代码块和静态成员的执行顺序由书写顺序决定,所以开始输出:
Insect
static Insect.x1 initialized
于是父类(Insect)的静态初始化结束。紧接着,类加载器继续加载子类(Beetle)的静态初始化,输出:
static Beetle.x2 initialized
Beetle
现在父类和子类的静态初始化操作全部完成。以上步骤仅仅是jvm试图调用Beetle类的main方法的准备动作,并没有真正执行main方法中的代码,现在开始真正执行:

public static void main(String[] args) {
prt("Beetle constructor");
Beetle b = new Beetle();
}

第一行输出
Beetle constructor
这没有疑问,显然易见的事。第二行开始实例化Beetle类,即开始对象的初始化了。我们看看Beetle类的构造器:

Beetle(){
prt("k="+k);
prt("j="+j);
}

它等价于:

Beetle(){
supper();
k=prt("Beetle.k initialized");
prt("k="+k);
prt("j="+j);
}

也就是在构造Beetle对象时需要先构造父类(Insect)对象。于是jvm又去执行Insect类的构造器:

Insect(){
j=prt("i="+i+",j="+j);
}

里面使用到了成员变量,其实成员变量的初始化也是放在构造器中的,也就是Insect类中对象成员属性定义等价于:

class Insect{
int i;
int j;
Insect(){
super();
i=9;
j=0;
j=prt("i="+i+",j="+j);
}
//其他部分省略
}

这样,实例化Insect对象时会输出:
i=9,j=0
注意,j的值,因为静态方法虽然能被子类继承但不能被重写,所以j的值是47而不是63,Insect类对象初始化完成。然后jvm回到Beetle构造器继续运行,完成Insect类的对象初始化,依次输出:
k=63
j=47


类初始华过程总结:

  1. 父类静态初始化
  2. 子类静态初始化
  3. 父类对象初始化
  4. 子类对象初始化

其他总结:

  1. 当类第一次被静态调用或是通过构造器调用时会被加载到内存
  2. 对象的成员属性初始化是在构造器中完成的(紧跟在supper()语句后面)
  3. 静态方法可以被继承,但是不能被重写,因为静态方法属于类,而不是属于对象