1 简介

方法句柄中包含两个重要的类,MethodHandle和MethodType

MethodHandle

通过句柄我们可以直接调用该句柄所引用的底层方法。从作用上来看,方法句柄类似于反射中的Method类,是对要执行的方法的一个引用,我们也是通过它来调用底层方法,它调用时有两个方法 invoke和invokeExact,后者要求参数类型与底层方法的参数完全匹配,前者则在有出入时做修改如包装类型。

MethodType

方法签名不可变对象,是对方法的一个映射,包含返回值和参数类型。在lookup时也是通过它来寻找的。 每个方法句柄都有一个MethodType实例,用来指明方法的返回类型和参数类型。

2 简单使用

2.1 示例测试

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(Test.class, "testMethod", methodType);
        //调用底层方法
        methodHandle.invoke(Test.class.newInstance(), "hello, world");
        //多个参数
        MethodHandle testMethod = MethodHandles.lookup().findVirtual(Test.class, "testMethod", MethodType.methodType(Object.class, String.class, int.class));
        testMethod.invoke(Test.class.newInstance(), "aa", 1);

        MethodType f3 = MethodType.methodType(Object.class, int.class, int.class);

        //查找静态方法
        MethodHandle aStatic = MethodHandles.lookup().findStatic(Test.class, "add", f3);

        //使用invokeExact调用时,参数类型和返回值类型必须与方法签名的一致
        aStatic.invokeExact(1, 1234);

        //调用静态方法 无法调用private的方法
        //aStatic.invoke( 1, 3);

    }

}
class Test {

    public void testMethod(String str) {
        System.out.println("testMethod : "+str);
    }

    public Object testMethod(String str, int x) {
        System.out.println("string = " + str+ " , int = " + x);
        return x;
    }

   /*private*/ public static Object add(int a, int b) {
        System.out.println("a + b = " + (a + b));
        return a + b;
    }
}

整体来说,比使用反射简单写,但是不能调用private方法,未深入了解,所以不清楚。

2.2 invoke和invokeExact的区别

invoke方法允许更加松散的调用方式。它会尝试在调用的时候进行返回值和参数类型的转换工作。这是通过MethodHandle类的asType方法来完成的,asType方法的作用是把当前方法句柄适配到新的MethodType上面,并产生一个新的方法句柄。当方法句柄在调用时的类型与其声明的类型完全一致的时候,调用invoke方法等于调用invokeExact方法;否则,invoke方法会先调用asType方法来尝试适配到调用时的类型。如果适配成功,则可以继续调用。否则会抛出相关的异常。这种灵活的适配机制,使invoke方法成为在绝大多数情况下都应该使用的方法句柄调用方式。

2.3 效率比较

//12244 12893 12434 12647 12473
    public void testReflect() throws Exception{
        Test test = new Test();
        Class cls = Test.class;
        Method testMethod = cls.getMethod("testMethod", String.class);
        Instant start = Instant.now();
        for (int i = 0; i < 3_000_000 ; i++) {
            testMethod.invoke(test, "a");
        }
        Instant end = Instant.now();
        Duration between = Duration.between(start, end);
        System.out.println("reflect : " + between.toMillis());
    }

    //10785 11093 10923 11002 11125
    public void testMethodHandle() throws Throwable {
        Test test = new Test();
        MethodType methodType = MethodType.methodType(void.class, String.class);
        MethodHandle testMethod = MethodHandles.lookup().findVirtual(Test.class, "testMethod", methodType);
        Instant start = Instant.now();
        for (int i = 0; i < 3_000_000 ; i++) {
            testMethod.invoke(test, "a");
        }
        Instant end = Instant.now();
        Duration between = Duration.between(start, end);
        System.out.println("method : " + between.toMillis());
    }

如上所述,执行testMethod方法3百万次。其中使用反射的5次耗时结果为

12244 12893 12434 12647 12473   ms

使用方法句柄的结果为:

10785 11093 10923 11002 11125 ms

总体上看,方法句柄的执行效率要比反射更好

3 反射与方法句柄异同

1、Reflection和MethodHandle机制本质上都是在模拟方法调用,但是Reflection是在模拟Java代码层次的方法调用,而MethodHandle是在模拟字节码层次的方法调用 2、Reflection中的Method对象远比MethodHandle机制中的MethodHandle对象所包含的信息要多。前者是方法在Java一端的全面映像,包含了方法的签名、描述符以及方法属性表中各种属性的Java端表示方式,还包含有执行权限等的运行期信息。而后者仅仅包含着与执行该方法相关的信息。通俗的话说,Reflection是重量级,而MethodHandle是轻量级 3、由于MethodHandle是对字节码的方法指令调用的模拟,那理论上虚拟机在这方面做的各种优化(如方法内联),在MethodHandle上也应当可以采用类似思路去支持(但目前实现还不完善)。而通过反射去调用方法则不行 4、Reflection API的设计目标是只为Java语言服务的,而MethodHandle则设计为可服务于所有Java虚拟机之上的语言,其中也包括了Java语言