多态的问题:把一个子类的对象赋值给父类以后,访问其实例变量,对象表现为父类的特征,当访问其方法时,又表现为子类的特征

更加详细的解释
举个栗子进行解释:

public class Test {
    class A{
        public int a = 1;
        public void test(){
            System.out.println("this is A method");
        }
    }

    class B extends A{
        public int a = 2;
        public void test(){
            System.out.println("this is B method");
        }
    }
    public static void main(String []args){
        //new了一个B 对象然后将其赋值给父类A
        A a = new Test().new B();
        //访问a中的变量a
        System.out.println(a.a);
        //调用a中的test方法
        a.test();
    }
}

输出结果:
1
this is B method

问题:为什么把一个子类的对象赋值给父类以后,访问其实例变量表现为父类的特征,当访问其方法时,又表现为子类的特征?

原因:

第一个问题:

A a = new Test().new B();
System.out.println(a.a);
       

首先,在这行代码中,我们要明白,A称为变量的静态类型,而B称为变量的实际类型。变量的静态类型在编译期就是可知的,而变量的实际类型是要在程序的运行期才能确定,编译器在编译程序的时候并不知道变量的实际类型是什么。

而当需要访问对象的一个实例变量的时候,是根据变量的静态类型决定的,这是发生在编译期的,在编译期是可知的。

第二个问题:

    a.test();

接下来便是最复杂的问题,先用一句话来解释,当需要访问一个对象的方法时,是在运行期根据变量的实际类型来决定的。

我们使用javap生成这段代码的字节码,方便后面进行分析;
至于如何使用javap来生成代码的字节码,可以参考这篇文章。
使用Javap命令生成字节码

public class com.auto.demo.Test.Test {
  public com.auto.demo.Test.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/auto/demo/Test/Test$B
       3: dup
       4: new           #3                  // class com/auto/demo/Test/Test
       7: dup
       8: invokespecial #4                  // Method "<init>":()V
      11: dup
      12: invokevirtual #5                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      15: pop
      16: invokespecial #6                  // Method com/auto/demo/Test/Test$B."<init>":(Lcom/auto/demo/Test/Test;)V
      19: astore_1
      20: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      23: aload_1
      24: getfield      #8                  // Field com/auto/demo/Test/Test$A.a:I
      27: invokevirtual #9                  // Method java/io/PrintStream.println:(I)V
      30: aload_1
      31: invokevirtual #10                 // Method com/auto/demo/Test/Test$A.test:()V
      34: return
}

接下来我们解析一下这段字节码

public class com.auto.demo.Test.Test {
  public com.auto.demo.Test.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

这一段是父类oobject(因为object对象是所有的没有显示继承的对象的父类)的初始化操作,调用object的init方法。

 public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/auto/demo/Test/Test$B
       3: dup
       4: new           #3                  // class com/auto/demo/Test/Test
       7: dup

这段是分别创建来Test和B对象。因为是内部类所以需要这样来创建B对象。
中间省略一些,直接看最重要的一段字节码

      24: getfield      #8                  // Field com/auto/demo/Test/Test$A.a:I
      27: invokevirtual #9                  // Method java/io/PrintStream.println:(I)V
      30: aload_1
      31: invokevirtual #10                 // Method com/auto/demo/Test/Test$A.test:()V
      34: return

其中24行显示其访问的是A中的a变量。

但是,31行显示其调用的是A.test()方法,那为什么又调用的是B的test方法呢?这就是java多态的体现了。要想理解这个首先我们需要了解一下invokevirtual指令的多态查找,invokevirtual指令运行时解析过程大致分为以下几个步骤:

1、找到操作数栈顶的第一个元素所指向的对象的实际类型,计作c;

2、如果在类型c中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回Java.lang.IllegalAccessError异常;

3、否则,按照继承关系从下往上对c的各个父类进行第2步的搜索和验证过程;

4、如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。

由于invokevirtual指令执行的第一步就是在运行期确定接收的类型,所以最后访问的就是B中的test方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值