某次逛论坛时发现一个非常有意思的题目,如下:
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 }
类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)方法。
以上就是一道题目引出的知识点,包括字节码的解释,静态动态绑定,泛型擦除。
原文:http://blog.csdn.net/tangyongzhe/article/details/43964843