简介
对于反射,相信从业 java 开发的人多多少少有些耳闻,在 jdk 1.7 为了间接调用方法引入了另一个新的 API ,即方法句柄。在方法句柄里面,有两个重要的类分别是 MethodType 和 MethodHandle,下面允许我做个名词解释。
- MethodType
方法签名不可变对象,即对方法的一个映射,包含返回值和参数类型。在 lookup 时也是通过它来寻找的。
每个方法句柄都有个 MethodType 的实例,用来指明方法的返回类型和参数类型。
- MethodHandle
通过句柄我们可以直接调用该句柄所引用的底层方法。从作用上来看,方法句柄类似于反射中的 Method 类,是对要执行的方法的一个引用,我们也是通过它来调用底层方法,它调用时有两个方法 invoke 和 invokeExact,后者要求参数类型与底层方法的参数完全匹配,前者则在有出入时做修改如包装类型。
示例
下面用一个简易的程序去测试句柄
package com.wwjd.dao.interfaces;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.time.Duration;
import java.time.Instant;
/**
* 句柄测试
*
* @author 阿导
* @CopyRight 万物皆导
* @Created 2019年02月25日 10:16:00
*/
public class MethodHandleDemo {
public static void main(String[] args) throws Throwable {
// 参数为返回值类型、参数类型 单个参数
MethodType methodType =MethodType.methodType(void.class,String.class);
// 声明定义的方法句柄,通过 lookup 对象得到方法句柄,参数为方法所在的类、方法名称,所匹配的方法签名
MethodHandle methodHandle = MethodHandles.lookup().findVirtual(Dao.class,"dao",methodType);
// 调用底层方法
methodHandle.invoke(Dao.class.newInstance(),"万物皆导");
Instant instant =Instant.now();
MethodHandle methodHandle1 = MethodHandles.lookup().findVirtual(Dao.class,"dao",MethodType.methodType(Integer.class,String.class,int.class));
Object o = methodHandle1.invoke(Dao.class.newInstance(), "万物皆导", 1);
System.out.println(o);
Duration duration =Duration.between(instant,Instant.now());
System.out.println(duration.toMillis());
// 若是静态方法
MethodHandle methodHandleStatic = MethodHandles.lookup().findStatic(Dao.class,"add",MethodType.methodType(Object.class,int.class,int.class));
Object invoke = methodHandleStatic.invoke(10, 20);
System.out.println(invoke);
}
}
class Dao{
public void dao(String dao){
System.out.println("dao:"+dao);
}
public Integer dao(String dao,int index){
System.out.println("dao:"+dao+",index:"+index);
return index;
}
public static Object add(int a,int b){
System.out.println("a+b="+(a+b));
return a+b;
}
}
虽然相比反射而言,句柄实现起来会更加简单,但是 MethodHandle 获得的方法引用,并不能突破访问权限本身的限制,比如private方法,就不能在类外被使用,这一点不如反射。另外 invoke 和 invokeExact 的区别如下:
- invokeExtract 要求更加精确 ,invoke方法允许更加松散的调用方式
因为 invoke 会做一些适配,如参数类型的转换,但 invokeExtract 则不会,所以为了书写方便尽可能的使用 invoke。
反射和句柄的区别如下
- Reflection 和 MethodHandle 机制本质上都是在模拟方法调用,但是 Reflection 是在模拟 Java 代码层次的方法调用,而 MethodHandle 是在模拟字节码层次的方法调用
- Reflection 中的 Method 对象远比 MethodHandle 机制中的 MethodHandle 对象所包含的信息要多。前者是方法在Java一端的全面映像,包含了方法的签名、描述符以及方法属性表中各种属性的 Java 端表示方式,还包含有执行权限等的运行期信息。而后者仅仅包含着与执行该方法相关的信息。通俗的话说,Reflection 是重量级,而 MethodHandle 是轻量级
- 由于 MethodHandle 是对字节码的方法指令调用的模拟,那理论上虚拟机在这方面做的各种优化(如方法内联),在 MethodHandle 上也应当可以采用类似思路去支持(但目前实现还不完善)。而通过反射去调用方法则不行
- Reflection API 的设计目标是只为 Java 语言服务的,而 MethodHandle 则设计为可服务于所有 Java 虚拟机之上的语言,其中也包括了 Java 语言