方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。Class文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局的中入口地址(相当于直接引用)。
解析
方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用,这种解析能成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。(调用目标在程序代码写好、编译器进行编译时必须确定下来)这类方法的调用称为解析(resolution)
静态方法和私有方法两大类
在Java虚拟机里面提供四条方法调用字节指令
1、invokestatic:调用静态方法
2、invokespecial:调用实例构造器<init>方法、和私有方法和父类方法。
3、invokevirtual:调用所有的虚方法
4、invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象
只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段确定唯一调用的版本(静态方法、私有方法、实例构造器和父类方法四类)它们在类加载的时候就会指导符号引用解析为该方法的直接引用(这些方法也称为非虚方法,相对的是虚方法但它不包括final方法)
The JavaTM Virtual Machine Specification
3.11.8 Method Invocation and Return Instructions
The following four instructions invoke methods:
- invokevirtual invokes an instance method of an object, dispatching on the (virtual) type of the object. This is the normal method dispatch in the Java programming language.
- invokeinterface invokes a method that is implemented by an interface, searching the methods implemented by the particular runtime object to find the appropriate method.
- invokespecial invokes an instance method requiring special handling, whether an instance initialization method(§3.9), a
private
method, or a superclass method. - invokestatic invokes a class (
static
) method in a named class.
The method return instructions, which are distinguished by return type, areireturn(used to return values of type boolean
, byte
,char
,short
, or int
), lreturn, freturn , dreturn, and areturn. In addition, the return instruction is used to return from methods declared to bevoid
, instance initialization methods, and class or interface initialization methods.
分派(Dispatch)
分派可能是静态的也可能是动态的,根据分派依据的宗量数可分为单分派和多分派。
静态分派单分派、静态多分派、动态单分派、动态多分派
重载
public class StaticDispatch {
static abstract class Human{
}
static class Man extends Human{
}
static class Woman extends Human{
}
public void sayHello(Human human){
System.out.println("human");
}
public void sayHello(Man man){
System.out.println("man");
}
public void sayHello(Woman woman){
System.out.println("woman");
}
public static void main(String[] args){
Human man = new Man();
Human woman = new Woman();
StaticDispatch sd = new StaticDispatch();
sd.sayHello(man);
sd.sayHello(woman);
}
}
==>
human
human
Human man = new Man();
Human 称为变量的静态类型(外观类型) Man称为变量的实际类型
Javac编译器根据参数的静态类型决定使用哪个重载版本
所有有依赖静态类型来定位方法执行版本的分派动作,都称为静态分派。静态分派的最典型应用就是方法重载
重写
由于invokevirtual指令执行的第一步就是在运行期确定接收都的实际类型(把类方法符号引用解析到的不同的直接引用上),这个过程就是Java方法重写的本质
这种在运行期根据实际类型确定方法执行版本的分派过程称为动态分派
[深入理解Java虚拟机:JVM高级特性与最佳实践]