如题!以前初步了解过class文件的加载步骤,大部分文章里都提到了:
类变量的初始化在构造方法之前!!!
这个很好理解,比如下面的代码
public class A{
int a = 1;
public A(){
System.out.println(a); //1
a = 3;
System.out.println(a); //3
}
}
上面代码很好理解吧,在构造函数里a已经初始化完成,直接输出1,再次赋值时会覆盖上个值。
那么我们再看下面的代码:
public class Main {
public static void main(String[] args){
new B();
}
static class A{
int a = 3;
public A(){
System.out.println("this is A "+a);
a = 2;
display();
}
public void display(){
System.out.println("this is A display "+a);
}
}
static class B extends A{
int a = 1;
public B(){
super();
System.out.println("this is B "+a);
a = 5;
display();
}
@Override
public void display(){
System.out.println("this is B display "+a);
}
}
}
输出结果大家还能猜到吗?
起初看到这个结果我也很诧异!!!
第二行的结果为什么会是0??不应该是1吗?(方法的重写,调用子类的display,访问子类的a)
然而事实就是如此,唯一能解释通的就是:
父类构造方法的调用在类变量的初始化之前!!!
如果接受这个概念的话,上面的结果就能解释通了,父类的构造方法中调用B的display,而此时B中的a还没有初始化,所以输出默认值,0;
同样的,下面这几行代码就也能接受了,父类的初始化(也就是父类的加载)必须在子类之前完成,不然这个类变量的super怎么访问父类的属性呢?
static class B extends A{
int a = super.a;
public B(){
System.out.println("this is B "+a);
a = 5;
display();
}
}
当然这种只靠猜,说服力可能还不太够,下面是调用javap命令后的输出内容,懂JVM指令的可以自行查看一下!(删除了构造方法中的输出和display中输出的字符串)
G:\WorkSpace\jedisTest\out\production\jedisTest>javap -c com.company.Main$B
Compiled from "Main.java"
class com.company.Main$B extends com.company.Main$A {
int a;
public com.company.Main$B();
Code:
0: aload_0
1: invokespecial #1 // Method com/company/Main$A."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field a:I
9: aload_0
10: iconst_5
11: putfield #2 // Field a:I
14: aload_0
15: invokevirtual #3 // Method display:()V
18: return
public void display();
Code:
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #2 // Field a:I
7: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
10: return
}
恕编者目前水平有限,对JVM还不太熟悉,有几个命令还不太熟悉,所以此处不做解释,一段时间后会再来续写,对每一行指令做出解释!但可以告诉大家执行的顺序:
父类类变量初始化->父类构造函数->子类类变量->子类构造函数。