Java:方法的虚分派(virtual dispatch)和方法表(method table)


背景知识:

java 字节码基本框架,jvm基本框架

多态的机制。


Virtual Dispatch

首先从字节码中对方法的调用说起。

java的bytecode中对方法的调用实现分为四种情况:

1.invokevirtual 为最常见的情况,包含virtual dispatch机制; 

2.invokespecial是作为private和构造方法的调用,绕过了virtual dispatch;

3.invokeinterface的实现跟invokevirtual类似。

4.invokestatic是对静态方法的调用。


其中最复杂的要属 invokevirtual .

 virtual dispatch机制会首先从 receiver(调用该方法的对象)的类的实现中查找对应的方法,如果没找到,则去父类查找,直到找到函数并实现调用,而不是依赖于引用类型

下面是一段有趣的代码。反映了virtual dispatch机制 和 一般的field访问的不同。

  1. public class Greeting {  
  2.     String intro = "Hello";  
  3.     String target(){  
  4.         return "world";  
  5.     }  
  6. }  
 
  1. public class FrenchGreeting extends Greeting {  
  2.     String intro = "Bonjour";  
  3.     String target(){  
  4.         return "le monde";  
  5.     }  
  6.       
  7.       
  8.     public static void main(String[] args){  
  9.         Greeting english = new Greeting();  
  10.         Greeting french = new FrenchGreeting();  
  11.           
  12.         System.out.println(english.intro + "," + english.target());  
  13.         System.out.println(french.intro + "," + french.target());  
  14.         System.out.println(((FrenchGreeting)french).intro + "," + ((FrenchGreeting)french).target());  
  15.     }  
  16. }  
运行的结果为
  1. Hello,world  
  2. Hello,le monde  
  3. Bonjour,le monde  
其中的第二行是亮点。

 对于intro这个filed的访问,直接指向了父类中的变量,因为引用 类型为父类。

而对于target的方法调用,则是指向了子类中的方法,虽然引用类型也为父类,但这是virtual dispatch的结果,virtual dispatch不管引用类型的,只查receiver的类型。


既然 虚分派 机制是从receiver对象的子类开始查找,由此看来,对于一个覆盖了父类中某方法的子类的对象,是无法调用父类中那个被覆盖的方法的吗?

在虚分派机制中这确实是不可以的。但却可以通过invokespecial实现。如下代码

  1. public class FrenchGreeting extends Greeting {  
  2.     String intro = "Bonjour";  
  3.     String target(){  
  4.         return "le monde";  
  5.     }  
  6.       
  7.     public String func(){  
  8.         return super.target();  
  9.     }  
  10.       
  11.       
  12.     public static void main(String[] args){  
  13.         Greeting english = new Greeting();  
  14.         FrenchGreeting french = new FrenchGreeting();  
  15.           
  16.         System.out.println(french.func());  
  17.           
  18.     }  
  19. }  
func()就成功的调用了父类的方法target,虽然target已经被子类重写了。怎么实现的?让我们看一看func方法中生成的字节码:
  1. ALOAD 0: this  
  2. INVOKESPECIAL Greeting.target() : String  
  3. ARETURN  
原来如此,它是通过invokespecial 指令来调用的。
  • 1
  • 2
  • 下一页

相关内容