代码:
class Father {
int x = 10;
public Father() {
this.print();
x = 20;
}
public void print() {
System.out.println("Father.x = " + x);
}
}
class Son extends Father {
int x = 30;
public Son() {
this.print();
x = 40;
}
public void print() {
System.out.println("Son.x = " + x);
}
}
public class Test {
public static void main(String[] args) {
Father f = new Son();
System.out.println(f.x);
}
}
结果:
Son.x = 0
Son.x = 30
20
解释:
非静态成员变量的加载过程
如下:
- 默认初始化
- 显式初始化 / 代码块中初始化(这两个的执行顺序取决于代码顺序)
- 构造器中初始化
- 有了对象之后,可以使用“对象.属性”或者“对象.方法()”的方式对成员变量进行赋值
注意:显式初始化、代码块中初始化、构造器中初始化都是在init()方法中进行的
由于以上代码中不涉及静态变量/常量,静态代码块等内容,所以以下分析中不在分析静态内容;
现在我们来分析程序,程序首先进入main()方法
,我们来看main()
方法的字节码文件,如下所示:
0 new #2 <com/atguigu/java/Son>
3 dup
4 invokespecial #3 <com/atguigu/java/Son.<init>>
7 astore_1
8 getstatic #4 <java/lang/System.out>
11 aload_1
12 getfield #5 <com/atguigu/java/Father.x>
15 invokevirtual #6 <java/io/PrintStream.println>
18 return
其中new #2 <com/atguigu/java/Son>
代表新建了Son
对象,dup
代表将该对象复制一份,invokespecial #3 <com/atguigu/java/Son.<init>>
代表执行Son类
的init()方法
,我们直接看Son类
的init()
方法的字节码文件,如下所示:
0 aload_0
1 invokespecial #1 <com/atguigu/java/Father.<init>>
4 aload_0
5 bipush 30
7 putfield #2 <com/atguigu/java/Son.x>
10 aload_0
11 invokevirtual #3 <com/atguigu/java/Son.print>
14 aload_0
15 bipush 40
17 putfield #2 <com/atguigu/java/Son.x>
20 return
其中aload_0
代表加载this
到操作数栈中,invokespecial #1 <com/atguigu/java/Father.<init>>
代表调用Father
父类的init()
方法,那我们直接看Father类
的init()
方法的字节码文件,如下所示:
0 aload_0
1 invokespecial #1 <java/lang/Object.<init>>
4 aload_0
5 bipush 10
7 putfield #2 <com/atguigu/java/Father.x>
10 aload_0
11 invokevirtual #3 <com/atguigu/java/Father.print>
14 aload_0
15 bipush 20
17 putfield #2 <com/atguigu/java/Father.x>
20 return
我们来好好分析一下,aload_0
代表加载this
到操作数栈中,invokespecial #1 <java/lang/Object.<init>>
代表调用Object
父类的init()
方法,这个里面没啥内容,所以就不在说了,bipush 10
代表将10压入操作数栈中,putfield #2 <com/atguigu/java/Father.x>
代表将10赋值给Father
对象中的变量x
,invokevirtual #3 <com/atguigu/java/Father.print>
本来是要执行Father
类中的print()
方法,但是实际执行的是Son
类中的print()
方法,这属于方法多态性,由于还没有对Son
类对象中的x变量赋值,执行print()
方法输出的值是0,因此结果中 Son.x = 0
,然后继续看Father
类的init()
方法,bipush 20
代表将20压入操作数栈中,putfield #2 <com/atguigu/java/Father.x>
代表将20赋值给Father
对象中的变量x
,所以Father
类执行init()
方法之后里面的变量x
的值是20
,这就完成了Father
类的init()
方法,我们回到Son类
中的init()
方法中,虽然上面已经写出了代码,这里在给大家写一下字节码文件,如下:
0 aload_0
1 invokespecial #1 <com/atguigu/java/Father.<init>>
4 aload_0
5 bipush 30
7 putfield #2 <com/atguigu/java/Son.x>
10 aload_0
11 invokevirtual #3 <com/atguigu/java/Son.print>
14 aload_0
15 bipush 40
17 putfield #2 <com/atguigu/java/Son.x>
20 return
bipush 30
代表将30压入操作数栈中,putfield #2 <com/atguigu/java/Son.x>
代表将30赋值给Son
对象中的变量x
,invokevirtual #3 <com/atguigu/java/Son.print>
将会调用Son类中的print()
方法,此时x等于30,所以结果是 Son.x = 30
,bipush 40
代表将40压入操作数栈中,putfield #2 <com/atguigu/java/Son.x>
代表将40赋值给Son
类对象中的变量x
到此为止,main()
方法中的Father f = new Son();
就已经执行完了,我们来看main()方法中的最后一行代码System.out.println(f.x);
,由于f
是Father
类对象,我们来看main()
方法的字节码文件,字节码如下:
0 new #2 <com/atguigu/java/Son>
3 dup
4 invokespecial #3 <com/atguigu/java/Son.<init>>
7 astore_1
8 getstatic #4 <java/lang/System.out>
11 aload_1
12 getfield #5 <com/atguigu/java/Father.x>
15 invokevirtual #6 <java/io/PrintStream.println>
18 return
astore_1
代表将this
存在局部变量表中下标为1
的位置,getfield #5 <com/atguigu/java/Father.x>
代表使用Father类
对象的变量x的值,当然这个值就是20,因此结果是 20
至此,完美结束。