您的位置:首页 > 编程语言 > Java开发

Java 多态实现的详细介绍

2017-11-08 16:43 239 查看
普通(非多态)方法的地址是在编译时确定的,调用它的字节码(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.toString
Double.toString


在这个程序中,直到运行时间才知道:

1: C:\Temp>javac PolymorphismDemo.java
2:
3: C:\Temp>java PolymorphismDemo 123
4: The number 123 has class class java.lang.Long
5:
6: C:\Temp>java PolymorphismDemo 123.456
7: 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 NameAddressComment
Object.toString111Object.toString method address
9 Additional methods
下一个最远的祖先类(这里是Number类)中的多态方法(和已修改的现有条目)被添加到这个列表。

如果您查看API(JavaDocs),您会发现Number类不会覆盖任何方法,但会在表中添加六个新的方法。 所以方法表中的toString条目保持不变:

Integer Method Table part 2

Method NameAddressComment
Object.toString111Object.toString method address
Number.intValue222Number.intValue method address
15 Additional methods
这一直持续到所有父类的方法表已经合并为止。 最后再用Integer类的多态方法更新方法表。 现在toString被覆盖了:

Integer Method Table part 3

Method NameAddressComment
Object.toString333Integer.toString method address
Number.intValue444Integer.intValue method address
Integer.parseInt555Integer.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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: