由一道题目引出的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)方法。

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

时间: 2024-07-30 10:12:39

由一道题目引出的java多态的相关文章

一道理解虚函数(多态)机制的题目

一道理解虚函数(多态)机制的题目(摘抄) 以下程序输出为 class Base { public: Base(int j): i(j) {} virtual~Base() {} void func1() { i *= 10; func2(); } int getValue() { return i; } protected: virtual void func2() { i++; } protected: int i; }; class Child: public Base { public:

一道很有意思的java线程题

这几天看结城浩的<java多线程设计模式>,跟着做一些习题,有几道题目很有意思,记录下自己的体会. 首先是题目(在原书212页,书尾有解答): public class Main { public static void main(String[] args) { try { Blackhole.enter(new Object()); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Blac

Java多态小总结

多态,又可以称为动态绑定,即在运行时确定类型,比如: 1 class A { 2 void draw(){ 3 //输出“A” 4 } 5 } 6 class B { 7 void draw(){ 8 //输出“B” 9 } 10 11 } 这种关系里,如果调用A a = new B(); 此时,被称为向上转型,a的类型可能在很早之前被生命,而在这时候被明确指明是其子类型, 我们如果要去调用draw()方法的时候,会调用输出“B”,这样,便是Java中的“多态”.我们称其为“向上转型”. 但是,

JAVA多态示例

这多态,我觉得是最利害的.在开发大型程序中. 但,也是需要经过足够多的实践经验才能随心利用的. class Quadrangle{ private Quadrangle[] qtest = new Quadrangle[6]; private int nextIndex = 0; public void draw(Quadrangle q){ if(nextIndex < qtest.length){ qtest[nextIndex] = q; System.out.println(nextIn

java多态讲解

JAVA多态 一.相关的类结构 class A ...{ //create by danielinbiti public String show(D obj)...{ return ("A and D"); } public String show(A obj)...{ return ("A and A"); } } class B extends A...{ public String show(B obj)...{ return ("B and B&q

Java多态特性:重载和覆写的比较

Java重载: 在同一个类中 方法具有相同的名字,相同或不同的返回值,但参数不同的多个方法(参数个数或参数类型) public class MethoDemo{ public static void main(String args[]){ int one = add(10,20) ; // 调用整型的加法操作 float two = add(10.3f,13.3f) ; // 调用浮点数的加法操作 int three = add(10,20,30) ; // 调用有三个参数的加法操作 Syst

Java多态-继承与清理

通过组合和继承方法来创建新类时,永远不必担心对象的清理问题,子对象通常会留给垃圾回收器进行处理.如果确是遇到清理问题,那必须用心为新的类创建dispose()方法(在这里我们选用此名).并且由于继承的缘故,如果我们有其他作为垃圾回收一部分的特殊清理动作,就必须在导出类中覆盖被继承的dispose()方法.当覆盖被继承的diopose()方法时,务必记住调用基类版本dispose()方法:否则,基类的清理动作就不会发生.下例便是一个证明: package polymorphism; class C

分析函数之初体验(一)——一道题目产生的兴趣

本来公司说是要做BI的,后来被改成了一个报表系统,失去了体验BI的机会,有些不爽. 报表系统是由顾问公司做,顾问公司说要看我们的SQL水平,给出了一份试题,其中有一题是这样的: t_hykbgjl 记录了会员卡每次的发生额(nFse) .余额(nYe)及卡号(sKH),对于同一会员卡,上一条记录的余额加上本次发生额应等于本次的余额.否则帐将不平.记录号为sJlbh,请写出列出所有会员卡不平帐记录的Sql语句. 从题目上分析,需要把上一行的余额拿下来进能计算,如果采用传统写法,可能需要很多的嵌套,

从JVM角度看Java多态

Java多态的三个必要条件: 1. 继承 2. 子类重写父类方法 3. 父类引用指向子类对象 然后看一个例子 输出结果为: 给出结论:当满Java多态的三个条件时,可以发现c.eat()调用的实际上是子类的eat,但c.age调用的还是父类的age,而c.play()则不会通过编译. 但是在java的引用中Father不但指定了c以何种方式访问内存,也规定了能够访问内存空间的大小. 我们看Father实例对象的大小是占两行,但Child实例对象占三行(这里就是简单量化一下). 所以虽然c指向的是