由一道题目引出的java多态

某次逛论坛时发现一个非常有意思的题目,如下:

class A<B> 
		{  
		 	  public String show(A obj)
	         {  
	                return ("A and A");  
	         }   
		 	  public String show(B obj)
		         {  
		                return ("A and B");  
		         }   
		}   
		class B extends A
		{  
		         public String show(B obj)
		         {  
		                return ("B and B");  
		         }  
		         public String show(A obj)
		         {  
		                return ("B and A");  
		         }   
		}  

    A a = new B();  
    B b = new B();  
    System.out.println(a.show(b));  
上面的代码正确的结果会输出B and A,刚开始看到的时候也感觉莫名其妙,实际上这里包含有不少知识点,稍微不留神就会弄错。题目本身输出的结果不重要,关键是我们要掌握里面的知识点,明白为什么会这样输出,最犀利的方式还是从字节码的角度来观察。同样javap命令输出上面代码的字节码。

Compiled from "Test.java"
class A extends java.lang.Object{
A();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public java.lang.String show(A);
  Code:
   0:   ldc     #2; //String A and A
   2:   areturn

public java.lang.String show(java.lang.Object);
  Code:
   0:   ldc     #3; //String A and B
   2:   areturn

Compiled from "Test.java"
class B extends A{
B();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method A."<init>":()V
   4:   return

public java.lang.String show(B);
  Code:
   0:   ldc     #2; //String B and B
   2:   areturn

public java.lang.String show(A);
  Code:
   0:   ldc     #3; //String B and A
   2:   areturn

}

public static void main(java.lang.String[]);
  Code:
   0:   new     #2; //class B
   3:   dup
   4:   invokespecial   #3; //Method B."<init>":()V
   7:   astore_1
   8:   new     #2; //class B
   11:  dup
   12:  invokespecial   #3; //Method B."<init>":()V
   15:  astore_2
   16:  getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   19:  aload_1
   20:  aload_2
   21:  invokevirtual   #5; //Method A.show:(LA;)Ljava/lang/String;
   24:  invokevirtual   #6; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   27:  return

}

上面是类A,类B和main方法的字节码,先来看下类A的字节码,可以很神奇的发现show(B obj)这个方法不见了,其实代码为了增加复杂性,故意将该方法写成show(B obj),实际上类A是一个泛型类,这里用符号B来表示的,可以换成任意的其他符号。众所周知的是java中泛型是伪泛型,在编译期会发生一个叫做类型擦除的动作,关于泛型的类型擦除有很多可以讲的东西,建议读者自行去百度一下。因此这里发生类型擦出后,实际存在的方法为show(Object obj)。这里是非常关键的一点。

类B的字节码没有什么特殊的内容,只是定义了两个方法show(B),show(A),不过要注意的是会覆写类A中的show(A)方法,同时继承show(Object obj),因此类B中有三个方法。

然后再来看下main方法的字节码,一步一步的过:

new指令在堆中分配类B需要的内存并初始化成员变量为默认值,返回执行该地址的指针压栈。

dup指令复制当前栈顶的元素

invokespecial调用B的初始化函数,消耗一个栈顶元素

astore_1将栈顶元素弹出赋值给局部变量表的第二个变量这里是A a

然后后面类似的操作

astore_2将栈顶元素弹出赋值给局部变量表的第三个变量这里是B b

后面三条指令连续三个压栈操作先压入out变量,然后是a,最后是b

invokevirtual方法很关键,这里虽然写的是A.show(A),但是不得不先提及两个概念

概念一:静态绑定

静态绑定指的是在编译期间就已经确定了要调用的方法,private、static和final修饰的方法都是静态绑定的,注意在java中只有方法才有绑定的概念。

概念二:动态绑定

动态绑定指的是在运行时根据对象实际的类型去寻找要调用的方法。JAVA 虚拟机调用一个类方法时(静态方法),它会基于对象引用的类型(通常在编译时可知)来选择所调用的方法。相反,当虚拟机调用一个实例方法时,它会基于对象实际的类型(只能在运行时得知)来选择所调用的方法,这就是动态绑定,是多态的一种。

介绍完这两个概念接着看invokevirtual指令,由于引用的类型为A,因此会首先搜索A的方法表信息,发现show(A)方法最符合,所以这里编译的时候绑定到A.show(A),但是在运行中会发生动态绑定,当发现实际对象类型为B时,会在B的方法表中寻找最合适的方法,如果没找到则向上寻找父类中合适的方法,这里由于B覆写了父类的show(A)方法,因此会调用B的show(A)方法。

以上就是一道题目引出的知识点,包括字节码的解释,静态动态绑定,泛型擦除。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值