JAVA中的继承分析(转载)
原文出处:http://grzrt.iteye.com/blog/1754904
为什么写这篇博客,之前对继承的理解知识大体理论上,最近有个同事问了个问题,发现对JAVA继承的底层实现相当模糊,结合《深入理解Java虚拟机:JVM高级特性与最佳实践》以及上网查的资料进行了一下深入学习。
程序:现在又两个父子类如下
- class Parent{
- public String str = "Parent";
- private int a = 10;
- public int getA() {
- return a;
- }
- }
- class Chield extends Parent {
- public String str = "Chield";
- private int a = 20;
- public int getA() {
- return a;
- }
- }
测试程序1:
- public class TestInherit {
- public static void main(String[] args) {
- Parent p1 = new Parent();
- Parent c1 = new Chield();
- TestInherit.sayInherit(p1);
- TestInherit.sayInherit(c1);
- Parent p2 = new Parent();
- Chield c2 = new Chield();
- TestInherit.sayInherit(p2);
- TestInherit.sayInherit(c2);
- }
- public static void sayInherit(Parent p) {
- System.out.println("Call Parent");
- }
- public static void sayInherit(Chield c) {
- System.out.println("Call Chield");
- }
- }
结果是:
Call Parent
Call Parent
Call Parent
Call Chield
首先介绍方法调用的四条字节码指令:
invokevirtual 调用对象的实例方法,根据对象的实际类型进行分配,
invokeinterface 调用由接口实现的方法,在运行时对象中找到相应的实现;
invokespecial 调用需要特殊处理的实例方法,即实例的初始化<init>、private方法或超类的方法;
invokestatic 调用静态方法(static方法)。
其余字节码操作指令不作介绍。
代码:
Parent c1 = new Chield();
中Parent 称为静态类型,Chield称为实际类型。
静态绑定:如果是private,static或者final方法或者构造器,那么编译时就可以准备知道应该调用哪个方法,这种调用方式成为静态绑定。对应的字节码指令是:invokespecila,invokestatic(应用:overload,由于发生在编译期所以voreload不是由虚拟机来执行的)
动态绑定:与静态绑定相对,方法调用在运行时才能决定的,就是动态绑定。对应的指令为:invokevirtual(应用:override)
invokevirtual指令的运行时解析过程大致如下:
1)找到操作数栈顶的第一个元素所指向的对象的实际类型, 记作C
2)若果在类型C中找到常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束,不通过则返回java.lang.IllegalAccessError异常,
3)否则,按照继承关系从下到上依次对C的各个父类进行第2步的搜索和验证,
4)如果始终没找到合适的方法,则抛出java.lang.AbstractMethodError异常。
上边测试程序的子节码如下:
- public class com.rt.TestInherit extends java.lang.Object{
- public com.rt.TestInherit();
- public static void main(java.lang.String[]);
- Code:
- 0: new #16; //class com/rt/Parent
- 3: dup
- 4: invokespecial #18; //Method com/rt/Parent."<init>":()V #初始化
- 7: astore_1 #局部变量表 索引为1的位置
- 8: new #19; //class com/rt/Chield
- 11: dup
- 12: invokespecial #21; //Method com/rt/Chield."<init>":()V #初始化
- 15: astore_2 #局部变量表 索引为1的位置
- 16: aload_1 #加载局部变量表 索引为1的位置reference类型值到 操作数栈
- 17: invokestatic #22; //Method sayInherit:(Lcom/rt/Parent;)V #采用静态绑定
- 20: aload_2 #加载局部变量表 索引为2的位置reference类型值到 操作数栈
- #<span style="color: #ff0000;">采用静态绑定</span>
- ,所以虽然c1变量的实际类型是Chiled,由于采用静态绑定方法参数是静态类型,因此输出“Call Parent”
- 21: invokestatic #22; //Method sayInherit:(Lcom/rt/Parent;)V
- 24: new #16; //class com/rt/Parent
- 27: dup
- 28: invokespecial #18; //Method com/rt/Parent."<init>":()V
- 31: astore_3
- 32: new #19; //class com/rt/Chield
- 35: dup
- 36: invokespecial #21; //Method com/rt/Chield."<init>":()V
- 39: astore 4
- 41: aload_3
- 42: invokestatic #22; //Method sayInherit:(Lcom/rt/Parent;)V
- 45: aload 4
- 47: invokestatic #26; //Method sayInherit:(Lcom/rt/Chield;)V
- 50: return
- public static void sayInherit(com.rt.Parent);
- Code:
- 0: getstatic #37; //Field java/lang/System.out:Ljava/io/PrintStream;
- 3: ldc #43; //String Call Parent
- 5: invokevirtual #45; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 8: return
- public static void sayInherit(com.rt.Chield);
- Code:
- 0: getstatic #37; //Field java/lang/System.out:Ljava/io/PrintStream;
- 3: ldc #52; //String Call Chield
- 5: invokevirtual #45; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 8: return
- }
主要疑点在红色标记处。
测试程序2:
- public class TestInherit {
- public static void main(String[] args) {
- Parent p1 = new Parent();
- Parent c1 = new Chield();
- System.out.println(p1.str);
- System.out.println(c1.str);
- System.out.println(p1.getA());
- System.out.println(c1.getA());
- }
- }
输出结果:
Parent
Parent
10
20
在Eclipse中,通过Debug查看c1中的属性如下图:
可以看出,子类中包含所有父类中的属性,这是由于在加载的时候会先加载父类。
System.out.println(p1.str);
System.out.println(c1.str);
对应字节码如下:
- 19: aload_1 #从局部变量表中加载父类到操作数栈
- 20: getfield #28; //Field com/rt/Parent.str:Ljava/lang/String;
- 23: invokevirtual #32; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 26: getstatic #22; //Field java/lang/System.out:Ljava/io/PrintStream;
- 29: aload_2 #从局部变量表中加载子类类到操作数栈
- 30: getfield #28; //Field com/rt/Parent.str:Ljava/lang/String;
- #调用invokevirtual指令,会采用动态分配,找到实际类型子类对象(new Chield()生成)
- #但是由于属性是静态绑定,所以导致输出的父类的属性
- #如果调用的是方法,那么就会通过动态类型绑定到子类对象上
- 33: invokevirtual #32; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 36: getstatic #22; //Field java/lang/System.out:Ljava/io/PrintStream;
原因是:在Java中,属性绑定到类型,方法绑定到对象!

浙公网安备 33010602011771号