方法调用的底层实现
在应用程序中,Java的运行是通过main方法作为入口,代码的执行站在JVM中实际上是方法的执行,方法的执行在JVM是怎样的一个执行过程就是这里要看的。
方法调用的字节码指令
方法调用指令,Java提供了5个字节码指令来调用不同的方法:
- invokestatic 用来调用静态方法
- invokespecial 用来调用私有实例的方法,构造器,super等
- invokevirtual 调用非私有实例方法等,比如public,protect等
- invokeinterface 接口
- invokedynamic 动态方法
非虚方法
非虚方法,值得是在编译期就已经确认了调用版本,而且这个版本是不可变的,这样的方法称为非虚方法。一般有静态方法,私有方法,实例构造器,父类方法4种。再加上上被 final 修饰的方法,这种在符合加载的时候就可以把符号引用转为直接引用。
private static void hellow(){
System.out.println("hellow");
}
虚方法
虚方法就是运行时可变的,很多时候JVM就是需要根据调用者的动态类型来确定调用目标的方法,这就是动态绑定的过程。基本上除了非虚方法就是虚方法。
分派
理解分派就是Java面向对象主要靠这三点:封装,继承和多态,其中这里涉及的就是多态。多态的实现有两种,一种是静态分派,一种是动态分派。
所谓的静态分派是在编译的时候就已经知道的例如下面的例子,Man和Woman都是继承Human,如果你直接new一个Human然后穿进去同样一个名字的方法,这种编译前就确定的就是静态分派。而如果你是编译前不知道类型的,例如直接new一个Man,编译器是不知道是调用哪个会调用父类的方法,除非你有强制的类型转换。
public class StaticDispatch {
static abstract class Human {
}
static class Man extends Human {
}
static class Woman extends Human {
}
public void sayHello(Human guy) {
System.out.println("hello,guy!");
}
public void sayHello(Man guy) {
System.out.println("hello,gentleman!");
}
public void sayHello(Woman guy) {
System.out.println("hello,lady!");
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
StaticDispatch sr = new StaticDispatch();
sr.sayHello(man);
sr.sayHello(woman);
//实际类型变化
Human human = new Man();
//静态类型变化
sr.sayHello((Man) human);
human = new Woman();
sr.sayHello((Woman) human);
}
}
方法表
在方法区中会建立一个方法表,使用虚方法表索引来替代元数据来提高查找性能。
Lambda 表达式
lambda是通过方法句柄来实现的,方法句柄是一个能够被执行的引用,可以指向静态方法和实例方法,以及虚构的get,set方法。invokedynamic 指令的底层,是使用方法句柄(MethodHandle)来实现的。方法句柄是一个能够被执行的引用,它可以指向静态方法和实例方法,以及虚 构的 get 和 set 方法,从以下案例中可以看到 MethodHandle 提供的一些方法。