深入理解类加载机制
之前的博客说了,类加载分为五个阶段
加载->链接(验证+准备+解析)->初始化(使用前的准备)->使用->卸载
今天,遇到一个很有趣的现象:
public class Dervied extends Base { private String name = "dervied"; public Dervied() { tellName(); printName(); } public void tellName() { System.out.println("Dervied tell name: " + name); } public void printName() { System.out.println("Dervied print name: " + name); } public static void main(String[] args){ new Dervied(); } }
class Base {
private String name = "base";
public Base() {
tellName();
printName();
}
public void tellName() {
System.out.println("Base tell name: " + name);
}
public void printName() {
System.out.println("Base print name: " + name);
}
}
猜猜输出的结果是什么,或许你会说
Dervied tell name: base
Dervied print name: base
Dervied tell name: dervied
Dervied print name: dervied
一开始我也是这么觉得的,但是,实际上输出的结果却是
Dervied tell name: null
Dervied print name: null
Dervied tell name: dervied
Dervied print name: dervied
为什么会是Null?,是不是很奇怪,下面我会详细分析
结合着 加载->链接(验证+准备+解析)->初始化(使用前的准备)->使用->卸载 这几个阶段,我们先来看看。
首先,new一个Dervied是调用他的构造方法,
public Dervied() {
tellName();
printName();
}
我们知道,创建一个子类的实例时,首先会调用其父类无参的构造方法,而调用无参的构造方法之前,会调用父类属性,即调用
private String name = "base";
然后
public Base() {
tellName();
printName();
}
而tellName();这句会输出
System.out.println("Base tell name: " + name);
你可能会想,此时不是已经读取了name吗,为什么最后显示的是Null呢,实际上,虽然此时父类的name是base,但是子类的name还是null,此刻调用的name不是父类的name,而是子类的name,即使写在父类里面。
所以输出的结果是上面我们看到的那样。
我们可以对上面的例子做一点改变:
把
private String name = "dervied";改成
private static String name = "dervied";
这样,就可以访问到子类的name了。
原因是static修饰的变量在类加载的时候就已经被载入了,而我们上面讨论的都是发生在类初始化的时候进行的,所以输出的结果是
Dervied tell name: dervied
Dervied print name: dervied
Dervied tell name: dervied
Dervied print name: dervied