8.3.1 解析

8.3.1 解析

承接前面关于方法调用的话题,所有方法调用的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用,这种解析能够成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。换句话说,调用目标在程序代码写好、编译器进行编译那一刻就已经确定下来。这类方法的调用被称为解析(Resolution)。

在Java语言中符合“编译期可知,运行期不可变”这个要求的方法,主要有静态方法和私有方法两大类,前者与类型直接关联,后者在外部不可被访问,这两种方法各自的特点决定了它们都不可能通过继承或别的方式重写出其他版本,因此它们都适合在类加载阶段进行解析。

调用不同类型的方法,字节码指令集里设计了不同的指令。在Java虚拟机支持以下5条方法调用字节码指令,分别是:

  • invokestatic。用于调用静态方法。
  • invokespecial。用于调用实例构造器<init>()方法、私有方法和父类中的方法。
  • invokevirtual。用于调用所有的虚方法。
  • invokeinterface。用于调用接口方法,会在运行时再确定一个实现该接口的对象。
  • invokedynamic。先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。前面4 条调用指令,分派逻辑都固化在Java虚拟机内部,而invokedynamic指令的分派逻辑是由用户设定的引 导方法来决定的。

只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本, Java语言里符合这个条件的方法共有静态方法、私有方法、实例构造器、父类方法4种,再加上被final 修饰的方法(尽管它使用invokevirtual指令调用),这5种方法调用会在类加载的时候就可以把符号引用解析为该方法的直接引用。这些方法统称为“非虚方法”(Non-Virtual Method),与之相反,其他方法就被称为“虚方法”(Virtual Method)。

代码清单8-5演示了一种常见的解析调用的例子,该样例中,静态方法sayHello()只可能属于类型StaticResolution,没有任何途径可以覆盖或隐藏这个方法。

代码清单8-5 方法静态解析演示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 方法静态解析演示
*
* @author zzm
*/
public class StaticResolution {
public static void sayHello() {
System.out.println("hello world");

}
public static void main(String[] args) {
StaticResolution.sayHello();
}
}

使用javap命令查看这段程序对应的字节码,会发现的确是通过invokestatic命令来调用sayHello()方法,而且其调用的方法版本已经在编译时就明确以常量池项的形式固化在字节码指令的参数之中(代码里的31号常量池项):

1
2
3
4
5
6
7
8
9
javap -verbose StaticResolution 
public static void main(java.lang.String[]);
Code:
Stack=0, Locals=1, Args_size=1
0: invokestatic #31; //Method sayHello:()V
3: return
LineNumberTable:
line 15: 0
line 16: 3

Java中的非虚方法除了使用invokestatic、invokespecial调用的方法之外还有一种,就是被final修饰 的实例方法。虽然由于历史设计的原因,final方法是使用invokevirtual指令来调用的,但是因为它也无 法被覆盖,没有其他版本的可能,所以也无须对方法接收者进行多态选择,又或者说多态选择的结果 肯定是唯一的。在《Java语言规范》中明确定义了被final修饰的方法是一种非虚方法。

解析调用一定是个静态的过程,在编译期间就完全确定,在类加载的解析阶段就会把涉及的符号引用全部转变为明确的直接引用,不必延迟到运行期再去完成。而另一种主要的方法调用形式:分派 (Dispatch)调用则要复杂许多,它可能是静态的也可能是动态的,按照分派依据的宗量数可分为单分派和多分派[^1]。这两类分派方式两两组合就构成了静态单分派、静态多分派、动态单分派、动态多分派4种分派组合情况,下面我们来看看虚拟机中的方法分派是如何进行的。

[^1]: 这里涉及的单分派、多分派及相关概念(如“宗量数”)在后续章节有详细解释,如没有这方面基础 的读者暂时略过即可。