Java 多态实现的详细介绍

来源:互联网 发布:淘宝为什么开不了店 编辑:程序博客网 时间:2024/05/22 10:05

普通(非多态)方法的地址是在编译时确定的,调用它的字节码(invokespecial,invokestatic)指令可以直接调用该方法。 这有时被称为早期绑定(或者叫做静态绑定),因为方法名称在编译时绑定到某一具体的内存地址。 这是有效的,但并不总是方便!
有时,我们不清楚某些变量的类型应该是什么,直到我们运行该程序,因为它可能取决于用户输入,随机数或其他外部数据,如文件中的数据。

以上所说的普通方法即为:private、static标识的方法。

对于如下的代码:

import java.text.NumberFormat;public class PolymorphismDemo {    public static void main(String[] args) throws Exception {        if (args.length == 0) {            System.out.println(                    "Usage: java PolymorphismDemo <some-number>");            return;        }        Number num = NumberFormat.getInstance().parse(args[0]);        System.out.println("The number " + num.toString() +                " has class " + num.getClass());    }}

创建的Number对象(在第10行)的实际类型取决于输入参数是一个整数(在这种情况下,它实际是一个Long对象)还是浮点数(在这种情况下,它实际是一个Double对象)。
在编译期间没有办法知道应该调用哪一个toString方法! 它取决于变量num引用哪种类型的对象,可以是Long.toStringDouble.toString
在这个程序中,直到运行时间才知道:

1: C:\Temp>javac PolymorphismDemo.java2: 3: C:\Temp>java PolymorphismDemo 1234: The number 123 has class class java.lang.Long5: 6: C:\Temp>java PolymorphismDemo 123.4567: The number 123.456 has class class java.lang.Double

那么编译器可以做什么?

编译main方法时,不能将方法名toString(在第12行使用)绑定到一个具体地址。 相反,编译器会将绑定推迟到运行期间,通过使用字节码(invokevirtual)来查找正确的toString方法的地址。
这就是为什么多态性也被称为晚期绑定,延迟绑定或动态绑定的原因。 (在某些语言中,如C++多态方法被称为虚方法或虚函数。)

没有多态性,这个程序会更复杂; 你必须使用一些自定义的方法来将args[0]字符串转换为一个数字,这也会以某种方式返回一个类型名称。
然后您需要一个switch语句或一个if表达式来测试类型名称并自己调用正确的方法。(即,Integer.toString(x)Double.toString(x))。
这种代码非常难看,很容易被破坏,难以维护或增强。 这是Java支持多态方法的主要原因。

多态是如何工作的?

对象的多态方法的地址存储在对象的方法表中。
当在运行期间调用一些多态方法时,在这个表中查找方法名称来获取相应的地址。
方法表包含对象的动态绑定(多态)方法的名称和地址。 对于属于同一个类的所有对象,方法表是相同的,所以它们作为该类的元数据存储在方法区中。

方法表不是语言的一部分,只要最终结果相同,不同的JVM供应商可以随意实现多态。
Sun JVM在对象的常量池中混合方法表项,可以使用命令javap -verbose foo查看。 (因为同一个类的所有对象都有相同的方法表,所以JVM可以把它保留在别的地方—-方法区。)

这是说明性的,看看系统如何为某些类(如Integer)构造一个方法表。 最初方法表是空的。 然后方法表用最远的祖先类(通常是Object类)中的多态方法填充:

Integer Method Table part 1

Method Name Address Comment Object.toString 111 Object.toString method address … … 9 Additional methods

下一个最远的祖先类(这里是Number类)中的多态方法(和已修改的现有条目)被添加到这个列表。
如果您查看API(JavaDocs),您会发现Number类不会覆盖任何方法,但会在表中添加六个新的方法。 所以方法表中的toString条目保持不变:
Integer Method Table part 2

Method Name Address Comment Object.toString 111 Object.toString method address Number.intValue 222 Number.intValue method address … … 15 Additional methods

这一直持续到所有父类的方法表已经合并为止。 最后再用Integer类的多态方法更新方法表。 现在toString被覆盖了:

Integer Method Table part 3

Method Name Address Comment Object.toString 333 Integer.toString method address Number.intValue 444 Integer.intValue method address Integer.parseInt 555 Integer.parseInt method address … … Additional methods

覆盖某些方法只会更改方法表中的地址,但不会更改名称。
这就是为什么javap输出将多态方法名称显示为Object.toString,而不仅仅是toString或Integer.toString。

看看这个方法调用的字节码(使用javap -verbose PolymorphismDemo),其内容如下:

40:  invokevirtual   #11; //Method java/lang/Object.toString:()Ljava/lang/String;

“#11”是指方法表中的方法名称(如上所述,这是常量池的索引),在这种情况下是名为Object.toString的方法返回一个字符串)。
invokevirtual字节码指令会导致JVM将#11中的值不视为地址(不作为早期绑定的地址),而是在方法表中查找当前对象的方法的名称来获得相应的索引

真正的执行逻辑为:在操作数栈获得对象的索引,然后调用该对象方法表中相同索引值的方法。

这就是它的工作原理!

此处#11 指的是常量池中的CONSTANT_Methodref_info
其中含有(CONSTANT_Class_info,CONSTANT_NameAndType_info ,CONSTANT_Utf8_info)


http://wpollock.com/Java/PolyMorphism.htm

原创粉丝点击