背景
JDK1.7以前字节码指令集中invokevirtual、invokespecial、invokestatic、invokeinterface 第一个参数都是被调用方法的符号引用(CONSTANT_Methodref_info或者CONSTANT_InterfaceMethodref_info常量),方法的符号引用是在编译时产生的,而动态类型语言只有在运行期才能确定接受者类型。
基于此,JDK1.7(JSR-292) 中invokedynamic指令以及java.lang.invoke包,此包主要目的是在之前单纯依靠符号引用来确定调用的目标方法这种方式以外,提供一个中新的动态确定目标方法的机制, 称为MethodHandle.
理解MethodHandle
如果要实现一个谓词的排序函数
C/C++
在C/C++中常用做法就是把谓词定义为函数, 用函数指针把谓词传递到排序方法中。
void sort(int list[], const int size, int (*compare) (int , int ))
Java
java中无法单独把一个函数作为参数传递,普遍的做法就是设计一个带有compare()方法的Comparator接口,以实现这个接口的对象作为参数,如Collcations.sort()
void sort(List list, Comparator c)
MethodHandle
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class MethodHandlerTest {
static class ClassA {
public void println(String message) {
System.out.println(message);
}
}
public static void main(String[] args) throws Throwable {
Object print = System.currentTimeMillis() % 2 == 0 ? System.out : new ClassA();
/**
* 无论print最终是哪个实现类,下面这句都可以正确调用到对应的println方法
*/
MethodHandle printlnMH = getPrintlnMH(print);
printlnMH.invokeExact("test");
}
private static MethodHandle getPrintlnMH(Object receiver) throws NoSuchMethodException, IllegalAccessException {
/**
* 这里的void 如果写为Void 则会报NoSuchMethodException
* MethodType: 代表方法类型, 包含了方法的返回值(methodType()的第一个参数)和具体参数(即第二个及之后的参数)
*/
MethodType methodType = MethodType.methodType(void.class, String.class);
/**
* MethodHandles.lookup()方法作用是在指定类中查找符合给定的方法名称、方法类型,并且符合调用权限的方法句柄
*/
return MethodHandles.lookup().findVirtual(receiver.getClass(), "println", methodType)
/**
* 这里调用的是虚方法, 按照java语言的规则,方法第一个参数是隐式的,代表该方法的接受者,
* 即this指向的对象, 这个参数以前是放在参数列表中国进行传递的, 而现在提供了bindTo()
* 方法来完成
*/
.bindTo(receiver);
}
}
getPrintlnMH()中模拟了invokevirtual指令执行的执行过程,只不过它的分派逻辑并非固化在Class文件的字节码中,而是通过一个具体的方法来实现,而这个方法本身的返回值(MethodHandle对象), 可以视为对最终调用方法的一个“引用”,。
void sort(List list, MethodHandle compare)
MethodHandle VS Reflection
相同点
1、 都是在模拟方法调用
不同点
Reflection
模拟Java代码层次的方法调用
方法在java一端的全面映像,包含方法的签名、描述符、以及方法属性表中各种属性的java端表示方式, 还包含了执行权限等的运行期信息
重量级
其API设计目标是为java语言服务的
MethodHandle
模拟字节码层次的方法调用
仅仅包含与执行该方法的相关信息,
轻量级
对字节码的方法指令进行模拟,所以理论上虚拟机可以在这方面对其做各种优化
其被设计成可服务于所有java虚拟机之上的语言, 其中包含java
MethodHandles.lookup()方法中的三个方法——findStatic()、 findVirtual()、 findSpecial()正是对应为了对应于invokestatic、invokevirtual & invokeinterface、invokespecial 这几个字节码指令的执行权限行为,而这些底层细节在使用Reflection API中是不需要关心的。
Son invoke GrandFather's method
GrandFather
public class GrandFather {
void thinking() throws Throwable {
System.out.println("i am grandfather");
}
}
Father
public class Father extends GrandFather {
@Override
void thinking() throws Throwable {
System.out.println("i am father");
}
}
Son
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class Son extends Father {
@Override
void thinking() throws Throwable {
/**
*
* MethodHandle 实现在调用GrandFather的thinking()方法
*/
MethodType methodType = MethodType.methodType(void.class);
MethodHandles.lookup().findSpecial(GrandFather.class, "thinking", methodType, this.getClass())
.invoke(this);
}
public static void main(String[] args) throws Throwable {
new Son().thinking();
}
}