java方法调用之多态的补充示例(四)

时间:2022-01-07 16:52:13

前一篇博文 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());
}
}

注意这里引用了成员变量。
执行结果如下所示:
java方法调用之多态的补充示例(四)
关键的字节码指令如下所示:

//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.introfrench.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指令调用的。
执行结果如下所示:
java方法调用之多态的补充示例(四)
这个的字节码指令需要看两个文件。

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不会有多态发生。

结束语

  1. 成员变量的访问只根据静态类型进行选择。
  2. 私有方法不会发生多态选择,只根据静态类型进选择。