`
grzrt
  • 浏览: 182601 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JAVA中的继承分析

    博客分类:
  • JAVA
阅读更多

    为什么写这篇博客,之前对继承的理解知识大体理论上,最近有个同事问了个问题,发现对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类型值到 操作数栈 
#采用静态绑定
,所以虽然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中,属性绑定到类型,方法绑定到对象!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics