java.lang.invoke包
动态类型方法调用的底层问题终归是应当在Java虚拟机层次上去解决才是最合适的。因此,在Java虚拟机层面上提供动态类型的直接支持就成为Java平台发展必须解决的问题,所以才有了java.lang.invoke包。
JDK7时新加入的java.lang.invoke包是JSR 292的一个重要组成部分,主要目标是之前单纯依靠符号引用来确定调用的目标方法这条路之外,提供一种新的动态确定目标方法的机制,称为“方法句柄”(Method Handle)。
举个例子,要实现一个带谓词(谓词就是由外部传入的排序时比较大小的动作)的排序函数,在C/C++中常引用做法是把谓词定义为函数,用函数指针来把谓词传递到排序方法,如下:
void sorr(int list[],const int size,int (*compare)(int ,int))
Java语言中做不到这一点,没办法单独把一个函数作为参数进行传递。普遍的做法是设计一个带有compare()方法的Comparator接口,以实现这个接口的对象作为参数,如Java类库中的Collections::sort()方法如下定义:
void sort(List list,Comparator c)
在拥有方法句柄之后,Java语言也可拥有类似于函数指针或委托的方法别名这样的工具了。以下代码演示了句柄的基本用法,无论obj是何种类型(临时定义的ClassA抑或是实现PrintStream接口的实现类System.out),都可正确调用到printIn()方法。
package com.test;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
/**
* JSR 292 MethodHandle基础用法演示
* @author huyl
*
*/
public class MethodHandleTest {
static class ClassA{
public void printIn(String s){
System.out.println(s);
}
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, Throwable {
Object obj = System.currentTimeMillis() % 2 == 0 ? System.out : new ClassA();
//无论obj最终是哪个实现类,下面这句都能正确调用到printIn方法
getPrintInMH(obj).invokeExact("icyfenix");
}
private static MethodHandle getPrintInMH(Object reveiver) throws NoSuchMethodException, IllegalAccessException{
/* MethodType:代表“方法类型”,包含了方法的返回值(methodType()的第一个参数)
* 和具体参数(methodType()的第二个参数)
*/
MethodType mt = MethodType.methodType(void.class, String.class);
/* lookup()方法来自于MethodHandles.lookup,这句的作用在指定类中查找符合给定的
* 方法名称、方法类型,并且符合调用权限的方法句柄。
* 因为这里调用的是一个虚方法,按照Java语言的规则,方法第一个参数是隐式的,代表该方法的
* 接收者,也即this指向的对象,这个参数以前是放在参数列表中进行传递,现在提供bindTo()
* 方法来完成这件事情
*/
return MethodHandles.lookup().findVirtual(reveiver.getClass(),"printIn",mt).bindTo(reveiver);
}
}
方法getPrintInMH()中实际上是模拟了invokevirtual指令的执行过程,只不过它的分派逻辑并非固话在Class文件的字节码上,而是通过一个由用户设计的Java方法来实现,而这个方法本身的返回值(MethodHandle对象),可以视为对最终调用方法的一个“引用”。由此为基础,有了MethodHandle就可写成类似C/C++那样的函数声明了:
void sort(List list,MethodHandle compare)
仅站在Java语言的角度看,MethodHandle在使用方法和效果上与Reflection有众多相似之处,不过他们也有以下区别:
- Reflection和MethodHandle机制本质上都是在模拟方法调用,但是Reflection是在模拟Java代码层次的方法调用,而MethodHandle是在模拟字节码层次的方法调用。在MethodHandles.lookup上的3个方法findStatic()、findVirtual()、findSpecial()正是为了对应于invokestatic、invokevirtual(以及invokeinterface)和invokespecial这几条字节码指令的执行权限检验行为,而这些底层细节在使用Reflection API时是不需要关心的。
- Reflection中的java.lang.reflect.Method对象远比MethodHandle机制中的java.lang.invoke.MethodHandle对象所包含的信息来的多。前者是方法在Java端的全面映像,包含了方法的签名、描述符 以及方法属性表中各种属性的Java端表示方式,还包含了执行权限等的运行期信息。而后者仅包含执行该方法的相关信息。Reflection是重量级的,MethodHandle是轻量级的。
- 由于MethodHandle是对字节码的方法指令调用的模拟,那理论上虚拟机在这方面做的各种优化(如方法内联),在MethodHandle上也应当可采用类似思路去支持(目前还在完善中),而通过反射去调用方法则几乎不可能直接去 实施各类调用点优化措施。
除了上诉区别,去除“仅站在Java语言的角度看”:Reflection API的设计目标是只为Java语言服务的,而MethodHandle则设计为可服务于所有Java虚拟机之上的语言,其中也包括了Java语言而已,而且Java在这里离并不是主角。
《深入理解Java虚拟机》第三版 学习
网友评论