前一篇博文 java方法调用之动态调用多态(重写override)的实现原理——方法表(三) 讲了多态的实现原理,这里写一个补充示例。
结论
方法表存放的只是invokevirtual和invokeinterface调用的方法,不包括invokestatic和invokespecial的静态方法、私有方法、构造器方法和父类方法,当然也不包括成员变量。普通public方法是通过invokevirtual指令调用的。
示例一
package org.fan.learn.polymorphism;
/**
* Created by Administrator on 2016/3/31.
*/
class Greeting {
String intro = "Hello";
String target(){
return "world";
}
}
public class FrenchGreeting extends Greeting {
String intro = "Bonjour";
String target(){
return "le monde";
}
public static void main(String[] args){
Greeting english = new Greeting();
Greeting french = new FrenchGreeting();
System.out.println(english.intro + "," + english.target());
System.out.println(french.intro + "," + french.target());
System.out.println(((FrenchGreeting)french).intro + "," + ((FrenchGreeting)french).target());
}
}
注意这里引用了成员变量。
执行结果如下所示:
关键的字节码指令如下所示:
//english.intro对应的字节码指令
26: aload_1 //局部变量表的1号slot中存放的就是english
27: getfield #11; //Field Greeting.intro:Ljava/lang/String;
//english.target()
38: aload_1
39: invokevirtual #14; //Method Greeting.target:()Ljava/lang/String;
//french.intro 对应的字节码指令
61: aload_2 //局部变量表的2号slot中存放的就是french
62: getfield #11; //Field Greeting.intro:Ljava/lang/String;
//french.target()
73: aload_2
74: invokevirtual #14; //Method Greeting.target:()Ljava/lang/String;
//((FrenchGreeting)french).intro
96: aload_2
97: checkcast #6; //class FrenchGreeting
100: getfield #3; //Field FrenchGreeting.intro:Ljava/lang/String;
//((FrenchGreeting)french).target()
111: aload_2
112: checkcast #6; //class FrenchGreeting
115: invokevirtual #17; //Method FrenchGreeting.target:()Ljava/lang/String;
这里只是给出了关于多态的字节码指令,前面的博文 由常量池 运行时常量池 String intern方法想到的(二)之class文件及字节码指令 中有讲到 String的+操作符都被优化成了StringBuilder。这里的System.out.println(english.intro + "," + english.target());
中的+也被优化了。
从字节码中可以看出,english.intro
与 french.intro
的字节码指令是一样的; english.target()
与 french.target()
的字节码指令也是一样的,调用的都是静态类型的成员变量和方法。但是经过强制类型转换之后,java编译器就会自动调用强制类型转换后的属性和方法。
也就是说:多态只适用于父子类同样签名的方法,而属性是不参与多态的。
示例二
package org.fan.learn.polymorphism;
/**
* Created by Administrator on 2016/3/30.
*/
public class BaseChildTest {
static class BaseClass {
//注意这个是私有方法
private void priMethod()
{
System.out.println("BaseClass scret");
}
public void print()
{
System.out.println("print_BaseClass");
}
public void baseMethod()
{
print();
priMethod();
}
}
static class ChildClass extends BaseClass {
//注意这个是私有方法,没有override BaseClass中的方法
private void priMethod()
{
System.out.println("ChildClass scret");
}
public void print()
{
System.out.println("print_ChildClass");
}
public void childMethod()
{
System.out.println("childMethod");
}
}
public static void main(String[] args) {
BaseClass bc = new ChildClass();
bc.print();
bc.baseMethod();
}
}
注意这里面有私有方法,私有方法不参与继承, 也不会出现在方法表中,因为私有方法是由invokespecial指令调用的。
执行结果如下所示:
这个的字节码指令需要看两个文件。
javap -verbose -c BaseChildTest
javap -verbose -c BaseChildTest$ChildClass
javap -verbose -c BaseChildTest$BaseClass
先看Main方法:
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=2, Args_size=1
0: new #2; //class BaseChildTest$ChildClass
3: dup
4: invokespecial #3; //Method BaseChildTest$ChildClass."<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4; //Method BaseChildTest$BaseClass.print:()V
12: aload_1
13: invokevirtual #5; //Method BaseChildTest$BaseClass.baseMethod:()V
16: return
从中可以看到调用的都是BaseClass中的方法。
看看BaseClass中的baseMethod方法:
public void baseMethod();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokevirtual #6; //Method print:()V
4: aload_0
5: invokespecial #7; //Method priMethod:()V
8: return
可以看到print方法使用的是invokevirtual,而priMethod使用的是invokespecial。根据在bc.baseMethod();调用时传递的this指针,print方法会发生多态的选择,而priMethod不会有多态发生。
结束语
- 成员变量的访问只根据静态类型进行选择。
- 私有方法不会发生多态选择,只根据静态类型进选择。