JAVA中的继承分析(转载)

原文出处:http://grzrt.iteye.com/blog/1754904

 

为什么写这篇博客,之前对继承的理解知识大体理论上,最近有个同事问了个问题,发现对JAVA继承的底层实现相当模糊,结合《深入理解Java虚拟机:JVM高级特性与最佳实践》以及上网查的资料进行了一下深入学习。
   程序:现在又两个父子类如下

 

Java代码  收藏代码
  1. class Parent{  
  2.     public String str = "Parent";  
  3.     private int a = 10;  
  4.     public int getA() {  
  5.         return a;  
  6.     }  
  7. }  
  8.   
  9. class Chield extends Parent {  
  10.     public String str = "Chield";  
  11.     private int a = 20;  
  12.     public int getA() {  
  13.         return a;  
  14.     }  
  15. }  

 

  测试程序1:

Java代码  收藏代码
  1. public class TestInherit {  
  2.     public static void main(String[] args) {  
  3.         Parent p1 = new Parent();  
  4.         Parent c1 = new Chield();  
  5.         TestInherit.sayInherit(p1);  
  6.         TestInherit.sayInherit(c1);  
  7.           
  8.         Parent p2 = new Parent();  
  9.         Chield c2 = new Chield();  
  10.         TestInherit.sayInherit(p2);  
  11.         TestInherit.sayInherit(c2);  
  12.     }  
  13.       
  14.     public static void sayInherit(Parent p) {  
  15.         System.out.println("Call Parent");  
  16.     }  
  17.       
  18.     public static void sayInherit(Chield c) {  
  19.         System.out.println("Call Chield");  
  20.     }  
  21. }  

 结果是:
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异常。
上边测试程序的子节码如下:

Java代码  收藏代码
  1. public class com.rt.TestInherit extends java.lang.Object{  
  2. public com.rt.TestInherit();  
  3. public static void main(java.lang.String[]);  
  4.   Code:  
  5.    0:   new     #16; //class com/rt/Parent  
  6.    3:   dup  
  7.    4:   invokespecial   #18; //Method com/rt/Parent."<init>":()V #初始化  
  8.    7:   astore_1    #局部变量表 索引为1的位置  
  9.    8:   new     #19; //class com/rt/Chield  
  10.    11:  dup  
  11.    12:  invokespecial   #21; //Method com/rt/Chield."<init>":()V #初始化  
  12.    15:  astore_2 #局部变量表 索引为1的位置  
  13.    16:  aload_1 #加载局部变量表 索引为1的位置reference类型值到 操作数栈   
  14.    17:  invokestatic    #22; //Method sayInherit:(Lcom/rt/Parent;)V #采用静态绑定  
  15.    20:  aload_2 #加载局部变量表 索引为2的位置reference类型值到 操作数栈   
  16. #<span style="color: #ff0000;">采用静态绑定</span>  
  17. ,所以虽然c1变量的实际类型是Chiled,由于采用静态绑定方法参数是静态类型,因此输出“Call Parent”  
  18.    21:  invokestatic    #22; //Method sayInherit:(Lcom/rt/Parent;)V   
  19.    24:  new     #16; //class com/rt/Parent  
  20.    27:  dup  
  21.    28:  invokespecial   #18; //Method com/rt/Parent."<init>":()V  
  22.    31:  astore_3  
  23.    32:  new     #19; //class com/rt/Chield  
  24.    35:  dup  
  25.    36:  invokespecial   #21; //Method com/rt/Chield."<init>":()V  
  26.    39:  astore  4  
  27.    41:  aload_3  
  28.    42:  invokestatic    #22; //Method sayInherit:(Lcom/rt/Parent;)V  
  29.    45:  aload   4  
  30.    47:  invokestatic    #26; //Method sayInherit:(Lcom/rt/Chield;)V  
  31.    50:  return  
  32.   
  33. public static void sayInherit(com.rt.Parent);  
  34.   Code:  
  35.    0:   getstatic       #37; //Field java/lang/System.out:Ljava/io/PrintStream;  
  36.    3:   ldc     #43; //String Call Parent  
  37.    5:   invokevirtual   #45; //Method java/io/PrintStream.println:(Ljava/lang/String;)V  
  38.    8:   return  
  39.   
  40. public static void sayInherit(com.rt.Chield);  
  41.   Code:  
  42.    0:   getstatic       #37; //Field java/lang/System.out:Ljava/io/PrintStream;  
  43.    3:   ldc     #52; //String Call Chield  
  44.    5:   invokevirtual   #45; //Method java/io/PrintStream.println:(Ljava/lang/String;)V  
  45.    8:   return  
  46. }  

 主要疑点在红色标记处。

 

测试程序2:

Java代码  收藏代码
  1. public class TestInherit {  
  2.     public static void main(String[] args) {  
  3.         Parent p1 = new Parent();  
  4.         Parent c1 = new Chield();  
  5.   
  6.         System.out.println(p1.str);  
  7.         System.out.println(c1.str);  
  8.          
  9.         System.out.println(p1.getA());  
  10.         System.out.println(c1.getA());  
  11.     }  
  12. }  

  输出结果:
Parent
Parent
10
20

 

在Eclipse中,通过Debug查看c1中的属性如下图:

 

可以看出,子类中包含所有父类中的属性,这是由于在加载的时候会先加载父类。

System.out.println(p1.str);
System.out.println(c1.str);
对应字节码如下:

Java代码  收藏代码
  1. 19:  aload_1 #从局部变量表中加载父类到操作数栈  
  2.    20:  getfield        #28; //Field com/rt/Parent.str:Ljava/lang/String;  
  3.    23:  invokevirtual   #32; //Method java/io/PrintStream.println:(Ljava/lang/String;)V  
  4.    26:  getstatic       #22; //Field java/lang/System.out:Ljava/io/PrintStream;  
  5.    29:  aload_2  #从局部变量表中加载子类类到操作数栈  
  6.    30:  getfield        #28; //Field com/rt/Parent.str:Ljava/lang/String;  
  7. #调用invokevirtual指令,会采用动态分配,找到实际类型子类对象(new Chield()生成)  
  8. #但是由于属性是静态绑定,所以导致输出的父类的属性  
  9. #如果调用的是方法,那么就会通过动态类型绑定到子类对象上  
  10.    33:  invokevirtual   #32; //Method java/io/PrintStream.println:(Ljava/lang/String;)V  
  11.    36:  getstatic       #22; //Field java/lang/System.out:Ljava/io/PrintStream;  

 原因是:在Java中,属性绑定到类型,方法绑定到对象!

posted @ 2016-07-01 09:14  青青子衿J  阅读(116)  评论(0)    收藏  举报