MethodHandle详解

从《Java虚拟机规范》中invokedynamic的描述可知invokedynamic的底层实现是基于java.lang.invoke.MethodHandle的,下面来详细探讨下MethodHandle的用法以及同Java Reflect的差异对比。

编程语言类型

1、解释型与编译型语言
解释型语言:是指通过解释器解释执行的语言,解释型语言不需要编译成与底层硬件平台直接交互的机器码语言,只要能被解释器识别即可,可以是源码或者类似于字节码的中间代码,典型的如JavaScript,Python,对应的解释器是谷歌V8 JavaScript执行引擎引入的解释器 Ignition,官方Python解释器CPython。

  • 编译型语言:是指可以通过编译器编译变成可以直接在底层硬件平台上直接运行的机器码,可以与底层硬件直接交互的语言,典型的如C/C++,C-object,操作系统如Linux,windows等,大小硬件设备的驱动程序基本都是用C写的。

  • 解释型语言因为必须经过中间的解释器解释执行,所以性能相对而言要差,但是语法规则可以更灵活,且因为解释器屏蔽了同底层硬件交互的细节,解释型语言学习成本更低,跨平台能力更强。编译型语言必须与底层硬件平台直接交互,能够基于底层平台特性做特定优化,所以性能更好,也是因此需要综合考虑并兼顾不同底层硬件平台的特性,代码更加复杂,学习成本高。

    Java可以通过默认的字节码模板解释器解释执行,也可以通过JIT即时编译器编译执行,通过-X+int选项指定以解释方式执行,通过-Xcomp选项指定以编译方式执行,通过-Xmixed,以解释+热点代码编译的方式执行,默认是-Xmixed。

2、静态类型语言与动态类型语言
静态类型语言:是指对数据类型/方法调用的检查是在编译期执行的,根据变量声明的类型来检查,典型的如Java

动态类型语言:是指对数据类型/方法调用的检查是在运行期执行的,根据变量实际的值类型来检查,典型的如JavaScript

Java的测试用例如下:

public class TypeTest {
 
    public static void test(String s){
        System.out.println("test:"+s);
    }
 
    public static void main(String[] args) {
        int a=12;
        TypeTest.test(a);
    }
}

执行javac编译报错,如下图:

image.png

如果是方法调用如TypeTest.test(a);会检查TypeTest类是否包含静态test方法。

JavaScript的测试用例如下:

function add(a,b){
  return a+b;
}
 
console.log(add(1,2));
 
console.log(add("a",2));

执行结果如下:

image.png

声明add方法时不需要声明变量a和变量b的类型,没有声明肯定就不会在编译时校验类型了,解释执行的时候会根据变量a和变量b的实际类型决定a+b的结果,如果两个都是数字则a+b表示两个数字相加,如果其中一个表示字符串则a+b表示将a和b以字串符的形式连接起来。

3、强类型语言和弱类型语言

  • 强类型语言是指一个变量如果被声明成什么类型了,在整个运行期该变量就一直是该类型,除非被强转成其他数据类型,典型的如Java

  • 弱类型语言是指一个变量在声明时不需要指定特定的类型,而是可以在运行时赋值成任一数据类型的值,典型的如JavaScipt。

Java的测试用例如下:

public class TypeTest {
 
    public static void main(String[] args) {
        int a=12;
        long b=13L;
        a=b;
    }
}

javac编译报错:

image.png

上述代码如果改成a=(int)b就不会报错,即将b强转成int赋值给a,b的类型依然是long。

javaScript的测试用例如下:

var a=12;
 
console.log(a);
 
a="test";
 
console.log(a);

执行结果如下:


image.png

变量a先是被赋值成整数类型,接着又被赋值成字符串类型,都可以正常执行。

4、动态语言和静态语言

  • 动态语言:指在运行时可以改变其结构的语言,如新的属性、函数、对象,甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化,典型的如JavaScript

  • 静态语言:指运行时结构不可变的语言,典型的如Java

JavaScript的测试用例如下:

class cla{
 
};
 
cla.a=1;
 
console.log(cla.a);
 
cla.test=function(){console.log("test");};
 
cla.test();

代码执行结果如下:


image.png

a和test不属于cla中定义的属性,而是在运行时动态添加的

Java测试用例如下:

public class TypeTest {
 
    public int a;
 
    public static void main(String[] args) {
        TypeTest typeTest=new TypeTest();
        typeTest.a=1;
        typeTest.b=2;
    }
}

javac编译报错,如下图:

image.png

属性a是TypeTest中声明的所以typeTest.a=1;没有报错,属性b不是TypeTest中声明的,所以报错找不到符号。

java.lang.invoke

1、JSR-292
java.lang.invoke是Java7实现JSR-292而引入的,为了在缺乏静态类型信息,运行时才能获取类型信息的情况下能够高效和灵活的执行方法调用,即在方法调用层面上提供对动态类型语言的支持,如执行obj.println("hello world"); 不再要求obj必须是java.io.PrintStream类型,只要在运行期obj定义了println方法即可。
因为之前定义的四个方法调用指令invokevirtual invokespecial invokestatic invokeinterface的第一个参数都是被调用方法的符号引用,根据符号引用可以解析出被调用方法的接收对象的类型和方法定义,即这四个指令要求在编译期必须确认被调用方法的接收对象的对象类型,如编译obj.println("hello world"); 必须确认obj的具体的类型,然后检查该类型是否定义了println方法,检查通过再据此生成一个被调用方法的符号引用。为了能够在JVM字节码指令层面实现在运行期才确认被调用方法的接收对象的类型,JVM配套的引入了一个新的方法调用指令invokedynamic,该指令同时也是Java8实现lamada的技术基础。注意java.lang.invoke包的底层实现并没有依赖invokedynamic指令,在非lamada下javac目前也不会生成invokedynamic指令,invokedynamic指令主要给同样是JVM上运行的动态类型语言使用,如Groovy。

测试用例如下:

package jni;
 
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Arrays;
import java.util.List;
 
class TestA{
    public void println(String s){
        System.out.println("TestA println:"+s);
    }
}
 
class TestB{
    public void println(String s){
        System.out.println("TestB println:"+s);
    }
}
 
public class MethodHandlerTest {
 
    public static void lamadaTest(){
        List<String> s= Arrays.asList("a","b","c");
        s.stream().forEach((String str) ->{System.out.println(str);});
    }
 
    public static void println(Object obj,String s) throws Throwable{
        MethodType mt=MethodType.methodType(void.class,String.class);
        MethodHandle methodHandle=MethodHandles.lookup().findVirtual(obj.getClass(),"println",mt);
        methodHandle.bindTo(obj).invokeExact(s);
    }
 
    public static void main(String[] args) throws Throwable{
        String test="Hello World";
        println(new TestA(),test);
        println(new TestB(),test);
        println(System.out,test);
        
        lamadaTest();
    }
}

上述用例中执行println方法时,obj的具体类型是不确定的,是在运行时才确认的,执行结果如下:


image.png

通过javap -v可以查看MethodHandlerTest的字节码,如下:

public static void lamadaTest();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=1, args_size=0
         0: iconst_3
         1: anewarray     #2                  // class java/lang/String
         4: dup
         5: iconst_0
         6: ldc           #3                  // String a
         8: aastore
         9: dup
        10: iconst_1
        11: ldc           #4                  // String b
        13: aastore
        14: dup
        15: iconst_2
        16: ldc           #5                  // String c
        18: aastore
        19: invokestatic  #6                  // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
        22: astore_0
        23: aload_0
        24: invokeinterface #7,  1            // InterfaceMethod java/util/List.stream:()Ljava/util/stream/Stream;
        29: invokedynamic #8,  0              // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
        34: invokeinterface #9,  2            // InterfaceMethod java/util/stream/Stream.forEach:(Ljava/util/function/Consumer;)V
        39: return
      LineNumberTable:
        line 24: 0
        line 25: 23
        line 26: 39
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           23      17     0     s   Ljava/util/List;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
           23      17     0     s   Ljava/util/List<Ljava/lang/String;>;
 
 
public static void println(java.lang.Object, java.lang.String) throws java.lang.Throwable;
    descriptor: (Ljava/lang/Object;Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=4, args_size=2
         0: getstatic     #2                  // Field java/lang/Void.TYPE:Ljava/lang/Class;
         3: ldc           #3                  // class java/lang/String
         5: invokestatic  #4                  // Method java/lang/invoke/MethodType.methodType:(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/invoke/MethodType;
         8: astore_2
         9: invokestatic  #5                  // Method java/lang/invoke/MethodHandles.lookup:()Ljava/lang/invoke/MethodHandles$Lookup;
        12: aload_0
        13: invokevirtual #6                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
        16: ldc           #7                  // String println
        18: aload_2
        19: invokevirtual #8                  // Method java/lang/invoke/MethodHandles$Lookup.findVirtual:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
        22: astore_3
        23: aload_3
        24: aload_0
        25: invokevirtual #9                  // Method java/lang/invoke/MethodHandle.bindTo:(Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;
        28: aload_1
        29: invokevirtual #10                 // Method java/lang/invoke/MethodHandle.invokeExact:(Ljava/lang/String;)V
        32: return
      LineNumberTable:
        line 22: 0
        line 23: 9
        line 24: 23
        line 25: 32
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      33     0   obj   Ljava/lang/Object;
            0      33     1     s   Ljava/lang/String;
            9      24     2    mt   Ljava/lang/invoke/MethodType;
           23      10     3 methodHandle   Ljava/lang/invoke/MethodHandle;
    Exceptions:
      throws java.lang.Throwable
 
 
public static void main(java.lang.String[]) throws java.lang.Throwable;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: ldc           #18                 // String Hello World
         2: astore_1
         3: new           #19                 // class jni/TestA
         6: dup
         7: invokespecial #20                 // Method jni/TestA."<init>":()V
        10: aload_1
        11: invokestatic  #21                 // Method println:(Ljava/lang/Object;Ljava/lang/String;)V
        14: new           #22                 // class jni/TestB
        17: dup
        18: invokespecial #23                 // Method jni/TestB."<init>":()V
        21: aload_1
        22: invokestatic  #21                 // Method println:(Ljava/lang/Object;Ljava/lang/String;)V
        25: getstatic     #24                 // Field java/lang/System.out:Ljava/io/PrintStream;
        28: aload_1
        29: invokestatic  #21                 // Method println:(Ljava/lang/Object;Ljava/lang/String;)V
        32: invokestatic  #25                 // Method lamadaTest:()V
        35: return
      LineNumberTable:
        line 35: 0
        line 36: 3
        line 37: 14
        line 38: 25
        line 40: 32
        line 41: 35
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      36     0  args   [Ljava/lang/String;
            3      33     1  test   Ljava/lang/String;
    Exceptions:
      throws java.lang.Throwable

从上述字节码可知java.lang.invoke包的相关方法调用并没有使用invokedynamic指令,lamada语句使用了invokedynamic指令。

2、MethodType
MethodType用来表示方法的入参类型和返回值类型,相当于期望的被调用方法的方法描述符,在执行MethodHandle#invokeExact方法、MethodHandle#invoke方法或者invokedynamic指令时,JVM会检查被调用方法与MethodType是否匹配。
一个MethodType由一个返回类型和任意多个参数类型组成,每个类型都是Class,如基本类型int.class,对象类型String.class,无返回值void.class。
所有的MethodType实例同String实例一样都是不可变的,当两个MethodType实例的返回类型和参数类型都相同则认为它们相等。MethodType只能通过工厂方法创建,传递参数类型时可以通过数组或者Listc传递。

MethodType可以通过方法描述符创建,这时方法描述符中涉及的类就必须已经完成加载,但是可以不初始化。

MethodType提供的方法可以分为以下几类:

1)工厂方法

image.png

其中genericMethodType方法是针对方法的返回值和参数类型都是Object时的methodType(java.lang.Class, java.lang.Class[])的简便方法。

2)修改返回参数类型和方法参数类型的方法

image.png

因为MethodType是不可变的,所以上述方法实际是在当前MethodType的基础上做适当修改然后构造了一个新的MethodType实例。

3)读取返回参数类型和方法参数类型的方法

image.png

其中 hasPrimitives方法判断MethodType的参数类型和返回类型中是否包含基本类型,hasWrappers判断是否包含包装器类型,如Interger。

4)对当前的MethodType进行转换的便利方法


image.png

erase方法将MethodType的参数类型和返回类型中包含的引用类型全部替换成Object.class

generic方法将MethodType的参数类型和返回类型中包含的所有类型全部替换成Object.class

wrap()方法将MethodType的参数类型和返回类型中包含的基本类型全部替换成对应的包装器类型

unwrap()方法将MethodType的参数类型和返回类型中包含的包装器类型全部替换成对应的基本类型

5)跟方法描述符字符串来回转换的方法:

image.png

3、MethodHandles.Lookup
Lookup类是MethodHandles的内部类,用public static final 修饰。Lookup就是一个创建MethodHandler的工厂类,Lookup在只在创建MethodHandler时检查执行lookup的类对目标方法的访问权限,而不是在通过MethodHandler调用特定方法时检查,Java反射API会每次调用都需要检查权限,这是Lookup与Java反射API最关键的不同。执行MethodHandles#lookup方法的类的Class作为构造方法参数构造Lookup实例,可以通过lookupClass()方法获取该Class,称之为lookup class。构造的Lookup实例也可以被共享,其他代码使用共享的Lookup实例时,依然使用创建Lookup实例时的类的Class来检查访问权限而非当前使用Lookup实例的类的Class。

Lookup类提供了多个用于创建操作构造方法,普通方法和字段的的MethodHandle的工厂方法,这些方法创建的MethodHandle的底层操作和对应字节码的底层操作是基本等同的,如下表。少数情况下不等同,参考MethodHandles.Lookup的注释说明。

image.png

表中C表示执行lookup的目标类,FT表示目标字段的类型,MT表示目标方法的MethodType,aMethod,aField,aConstructor表示反射中的Method,Field,Constructor实例;表中最后一个unreflect方法应该是unreflectSpecial,官方注释有误。比如执行Lookup#findGetter方法获取MethodHandle,通过该MethodHandle获取某个类FT的属性f,其底层操作上相当于this.f对应的字节码getfiled。表中的findSpecial方法需要重点关注,findSpecial方法要求最后一个参数Class<?> specialCaller与lookup 实例保存的lookup class一样且lookup实例保存的lookup class具有访问第一个参数Class<?> refc类的私有成员的权限,这样做的目的是将lookup能够访问的私有成员限制在lookup class能够访问的私有成员范围内。

除上述方法外,Lookup类还包含如下方法:

  • int lookupModes():获取Lookup实例能够访问的成员类型,其结果是取Lookup实例的allowedModes属性同PACKAGE (0x08)的并运算,allowedModes只能是PUBLIC (0x01), PRIVATE (0x02), PROTECTED (0x04),ALL_MODES
    四个掩码之一。
  • MethodHandles.Lookup in(Class<?> requestedLookupClass):用一个新的lookupClass创建一个新的Lookup实例,但是新实例能够访问的成员比原来的少。
  • MethodHandle bind(Object receiver,String name,MethodType type):从receiver对应的Class中查找目标方法,如果存在且当前Lookup实例通过findVirtual可以访问该方法,则将receiver绑定到Lookup实例创建的MethodHandle上。
    测试用例如下:
import java.lang.invoke.MethodHandles;
 
interface interfaceTest{
    void interTest();
}
 
class superA{
 
    public void println(String s){
        System.out.println("superA println:"+s);
    }
 
}
 
public class TestB extends superA implements interfaceTest{
 
    public int a;
 
    public static int b;
 
    private int c;
 
    public TestB(){
        this.a=11;
        b=12;
        c=13;
    };
 
    TestB(int a,String s){
        System.out.println("a="+a+",s="+s);
        this.a=21;
        b=22;
        c=23;
    }
 
    public void println(String s){
        System.out.println("TestB println:"+s);
    }
 
    private void privateTest(String s){
        System.out.println("TestB privateTest:"+s);
    }
 
    public static void staticPrintln(String s){
        System.out.println("TestB staticPrintln:"+s);
    }
 
    public static MethodHandles.Lookup lookup(){
        return MethodHandles.lookup();
    }
 
    @Override
    public void interTest() {
        System.out.println("TestB interTest");
    }
 
    @Override
    public String toString() {
        return "TestB{" +
                "a=" + a +
                ",b=" + b +
                ",c=" + c +
                '}';
    }
}
import org.junit.Test;
 
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
 
public class LookupTest{
 
    @Test
    public void testField() throws Throwable {
        TestB testB=new TestB();
        MethodHandles.Lookup lookup=MethodHandles.lookup();
        MethodHandle setter=lookup.findSetter(TestB.class,"a",int.class);
        setter.bindTo(testB).invoke(2);
        MethodHandle staticSetter=lookup.findStaticSetter(TestB.class,"b",int.class);
        staticSetter.invoke(3);
        System.out.println("setter result->"+testB);
 
        MethodHandle getter=lookup.findGetter(TestB.class,"a",int.class);
        int a=(int)getter.bindTo(testB).invoke();
        MethodHandle staticGetter=lookup.findStaticGetter(TestB.class,"b",int.class);
        int b=(int)staticGetter.invoke();
        System.out.println("getter result TestB a="+a+",b="+b);
 
        Field aField=TestB.class.getField("a");
        aField.setInt(testB,4);
        Field bField=TestB.class.getField("b");
        bField.setInt(null,5);
        System.out.println("reflect setter result->"+testB);
 
        a=aField.getInt(testB);
        b=bField.getInt(null);
        System.out.println("reflect getter result TestB a="+a+",b="+b);
 
        //无法访问私有属性,报错member is private: TestB.c/int/putField, from MethodHandleTest
//        MethodHandle privateSetter=lookup.findSetter(TestB.class,"c",int.class);
        //返回的lookup实例的lookup class就是TestB.class,所以可以访问私有成员
        lookup=TestB.lookup();
        MethodHandle privateSetter=lookup.findSetter(TestB.class,"c",int.class);
        privateSetter.bindTo(testB).invoke(6);
        System.out.println("findSetter private->"+testB);
 
        //无法访问私有属性,报错java.lang.NoSuc
//        Field cField=TestB.class.getField("c");
        Field cField=TestB.class.getDeclaredField("c");
        //必须执行setAccessible(true); 否则报错Class MethodHandleTest can not access a member of class TestB with modifiers "private"
        cField.setAccessible(true);
        cField.setInt(testB,7);
        System.out.println(testB);
        int c=cField.getInt(testB);
        System.out.println("private get c="+c);
    }
 
    @Test
    public void constuctTest() throws Throwable{
        MethodHandles.Lookup lookup=MethodHandles.lookup();
        //构造函数的返回类型是void
        MethodHandle defaultConstructor=lookup.findConstructor(TestB.class, MethodType.methodType(void.class));
        TestB testB=(TestB) defaultConstructor.invoke();
        System.out.println("defaultConstructor:"+testB);
 
        MethodHandle paramConstructor=lookup.findConstructor(TestB.class, MethodType.methodType(void.class,int.class,String.class));
        testB=(TestB) paramConstructor.invoke(1,"test");
        System.out.println("param constructor:"+testB);
 
        Constructor defaultConstructor2=TestB.class.getConstructor();
        testB=(TestB) defaultConstructor2.newInstance();
        System.out.println("reflect defaultConstructor:"+testB);
        testB=TestB.class.newInstance();
        System.out.println("reflect newInstance:"+testB);
 
        //getConstructor是查找公开的构造方法,此构造方法是默认的包级访问,所以getConstructor报错
//        Constructor paramConstructor2=TestB.class.getConstructor(int.class,String.class);
        //getDeclaredConstructor是获取声明的构造方法,获取的Constructor执行newInstance方法时会校验当前类是否
        //具备访问权限,如果没有权限需要执行setAccessible(true);
        Constructor paramConstructor2=TestB.class.getDeclaredConstructor(int.class,String.class);
        testB=(TestB) paramConstructor2.newInstance(7,"ts");
        System.out.println("reflect paramConstructor2:"+testB);
    }
 
    @Test
    public void methodTest() throws Throwable {
        MethodHandles.Lookup lookup=MethodHandles.lookup();
        MethodHandle staticHandle=lookup.findStatic(TestB.class,"staticPrintln",MethodType.methodType(void.class,String.class));
        staticHandle.invoke("static test");
 
        //方法没有入参,则不用传任何类型
        MethodHandle interfaceHandle=lookup.findVirtual(interfaceTest.class,"interTest",MethodType.methodType(void.class));
        interfaceHandle.bindTo(new TestB()).invoke();
 
        MethodHandle virtualHandle=lookup.findVirtual(superA.class,"println",MethodType.methodType(void.class,String.class));
        // 如果使用TestB.class,则执行bindTo(new superA())时java.lang.ClassCastException: Cannot cast superA to TestB
//        MethodHandle virtualHandle=lookup.findVirtual(TestB.class,"println",MethodType.methodType(void.class,String.class));
        virtualHandle.bindTo(new superA()).invoke("super test");
        virtualHandle.bindTo(new TestB()).invoke("sub test");
 
        //findSpecial方法要求第四个参数specialCaller和lookup class一致或者lookup class可以访问私有成员,即必须在目标类中创建lookup
        //否则报错no private access for invokespecial: class TestB, from MethodHandleTest
        //可以通过findSpecial调用私有方法,父类方法
//        MethodHandle specialHandle=lookup.findSpecial(TestB.class,"println",MethodType.methodType(void.class,String.class),TestB.class);
        lookup=TestB.lookup();
        MethodHandle specialHandle=lookup.findSpecial(TestB.class,"println",MethodType.methodType(void.class,String.class),TestB.class);
        specialHandle.bindTo(new TestB()).invoke("findSpecial test");
        specialHandle=lookup.findSpecial(superA.class,"println",MethodType.methodType(void.class,String.class),TestB.class);
        specialHandle.bindTo(new TestB()).invoke("findSpecial test");
 
        //调用私有方法
        specialHandle=lookup.findSpecial(TestB.class,"privateTest",MethodType.methodType(void.class,String.class),TestB.class);
        specialHandle.bindTo(new TestB()).invoke("findSpecial privateTest");
 
        Method method=superA.class.getMethod("println",String.class);
        //如果是TestB.class,则执行invoke new superA()时报错object is not an instance of declaring class
//        Method method=TestB.class.getMethod("println",String.class);
        method.invoke(new superA(),"reflect super test");
        method.invoke(new TestB(),"reflect sub test");
 
        method=TestB.class.getDeclaredMethod("privateTest",String.class);
        method.setAccessible(true);
        method.invoke(new TestB(),"reflect private test");
    }
 
    @Test
    public void reflectTest() throws Throwable {
        MethodHandles.Lookup lookup=MethodHandles.lookup();
        TestB testB=new TestB();
        Field aField=TestB.class.getField("a");
        MethodHandle setter=lookup.unreflectSetter(aField);
        setter.bindTo(testB).invoke(22);
        System.out.println(testB);
 
        MethodHandle getter=lookup.unreflectGetter(aField);
        int a=(int)getter.bindTo(testB).invoke();
        System.out.println("testB a="+a);
 
        Method method=superA.class.getMethod("println",String.class);
        MethodHandle methodHandle=lookup.unreflect(method);
        methodHandle.bindTo(testB).invoke("unreflect test");
 
        //报错no private access for invokespecial: class TestB, from MethodHandleTest
//        MethodHandle specialMethodHandle=lookup.unreflectSpecial(method,TestB.class);
        MethodHandle specialMethodHandle=TestB.lookup().unreflectSpecial(method,TestB.class);
        //调用TestB中println方法的父类实现
        specialMethodHandle.bindTo(testB).invoke("unreflectSpecial test");
 
        Constructor paramConstructor=TestB.class.getDeclaredConstructor(int.class,String.class);
        MethodHandle constuctorMethodHandle=lookup.unreflectConstructor(paramConstructor);
        TestB testB2=(TestB) constuctorMethodHandle.invoke(33,"unreflectConstructor test");
        System.out.println(testB2);
    }
}

4、MethodHandle
一个MethodHandle实例表示一个可以直接执行的对普通方法,构造方法,字段等操作或者其他的低级别的操作的引用。MethodHandle是跟方法的返回值类型和参数类型强相关的,它们并不是根据方法名或者定义方法的类来区分的,通过MethodHandle调用某个方法时要求目标方法的描述符必须与MethodHandle的类型描述符(即关联的MethodType实例)一致,可通过MethodHandle#type()方法获取关联的MethodType实例,MethodType实例决定了MethodHandler支持调用的方法类型。

MethodHandle有两个执行具体方法调用的方法 invokeExact和invoke,invokeExact要求被调用方法的描述符与MethodHandle完全一致,而invoke则允许适当的不一致,如果一致其执行速度和invokeExact一样,如果不一致invoke会尝试通过MethodHandle#asType(MethodType newType)方法创建一个跟目标方法一致的MethodType,然后利用该MethodType执行invokeExact方法。但是不能保证asType会被调用,如果JVM可以自动适配目标方法的参数就会直接调用了。

MethodHandle实例本身是不可变的,也没有任何可见的状态,相当于一个final变量。MethodHandle是一个抽象类但是不建议开发者实现一个子类,因为MethodHandler的类继承体系未来可能随着时间的改变而改变。

MethodHandle提供的方法可以分为以下几类:

1)方法调用:

Object invokeExact(Object... args):要求执行调用时的参数类型与返回值类型与MethodHandle关联的MethodType完全一致
Object invoke(Object... args):跟invokeExact相比,不要求完全一致,invoke会通过判断执行调用时的参数类型与返回值类型能否转换成MethodHandle关联的MethodType,如果能转换则通过MethodHandle asType(MethodType newType)方法生成一个新的MethodHandle,对新的MethodHandle执行invokeExact方法。
MethodHandle bindTo(Object x):将x作为invoke或者invokeExact的第一个参数,即方法调用的接收对象,如果没有执行该方法,则invoke或者invokeExact将第一个参数作为方法调用的接收对象。
Object invokeWithArguments(Object... arguments) :执行方法调用,要求方法参数是多个跟arguments个数一致的Object
Object invokeWithArguments(java.util.List<?> arguments),同上

2)MethodHandler转换

MethodHandle asType(MethodType newType) :用新的MethodType 创建一个新的MethodHandle
MethodHandle asSpreader(Class<?> arrayType, int arrayLength): 支持将调用参数中的数组的元素转换成方法入参
MethodHandle asCollector(Class<?> arrayType, int arrayLength): 将可变参数个数的MethodHandle转换成固定参数个数的
MethodHandle asVarargsCollector(Class<?> arrayType):将数组形式的入参转换成支持可变参数个数的MethodHandle
MethodHandle asFixedArity() :将参数类型固定,不允许invoke调用时做类型转换

测试用例如下:

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.WrongMethodTypeException;
import java.util.Arrays;
import java.util.List;
 
import static java.lang.invoke.MethodHandles.publicLookup;
import static java.lang.invoke.MethodType.methodType;
import static org.junit.Assert.assertEquals;
 
public class MethodHandleTest {
 
    @Test
    public void invokeTest() throws Throwable {
        MethodHandles.Lookup lookup=MethodHandles.lookup();
        MethodHandle methodHandle=lookup.findStatic(TestB.class,"staticPrintln", methodType(void.class,Object.class));
        //invoke内部会调用asType产生一个适配的MethodType,不过能够适配的类型非常有限
        //能够适配的逻辑参考MethodType#canConvert方法
        methodHandle.invoke(122);
        //invokeExact要求类型严格匹配,test是String不是Object类型
//        methodHandle.invokeExact("test");
 
        MethodType newType=methodHandle.type().changeParameterType(0,String.class);
        methodHandle=methodHandle.asType(newType);
        methodHandle.invokeExact("test");
 
    }
 
    @Test
    public void asVarargsCollectorTest() throws Throwable {
        MethodHandles.Lookup lookup=MethodHandles.lookup();
        //Arrays#deepToString方法本来的参数类型是Object[],不是不可变参数类型
        MethodHandle methodHandle=lookup.findStatic(Arrays.class,"deepToString", methodType(String.class,Object[].class));
        //返回false,表示不支持可变参数
        System.out.println(methodHandle.isVarargsCollector());
        //调用报错cannot convert MethodHandle(Object[])String to (int,int,int,int)List
//        String result=(String)methodHandle.invoke(1,2,3,4);
        String result=(String)methodHandle.invoke(new Object[]{1,2,3,4});
        System.out.println(result);
 
        //asVarargsCollector将其转换成支持可变参数个数的MethodHandle
        //如果MethodHandle对应的方法本身就支持可变参数则不做任何转换
        methodHandle=methodHandle.asVarargsCollector(Object[].class);
        result=(String)methodHandle.invoke(1,2,3,4);
        System.out.println(result);
 
        result=(String)methodHandle.invoke(new Object[]{11,12,13,14});
        System.out.println(result);
 
        methodHandle=lookup.findStatic(Arrays.class,"asList", methodType(List.class,Object[].class));
        System.out.println(methodHandle.isVarargsCollector());
        List list=(List) methodHandle.invoke(21,22,23,24);
        System.out.println(list);
    }
 
    @Test
    public void asCollectorTest() throws Throwable {
        MethodHandles.Lookup lookup=MethodHandles.lookup();
        MethodHandle methodHandle=lookup.findStatic(Arrays.class,"deepToString", methodType(String.class,Object[].class));
        methodHandle=methodHandle.asCollector(Object[].class,3);
        System.out.println(methodHandle.type().equals(methodType(String.class,Object.class,Object.class,Object.class)));
 
        //将原来的Object数组变成指定参数个数,如果个数不符报错cannot convert MethodHandle(Object,Object,Object)String to (String,String)String
//        String result=(String)methodHandle.invoke("a","b");
//        new Object[]被当成一个对象,报错cannot convert MethodHandle(Object,Object,Object)String to (Object[])String
//        String result=(String)methodHandle.invoke(new Object[]{"a","b","c"});
        String result=(String)methodHandle.invoke("a","b","c");
        System.out.println(result);
        System.out.println(methodHandle.isVarargsCollector());
    }
 
    @Test
    public void asSpreaderTest() throws Throwable {
        MethodHandle equals = publicLookup()
                .findVirtual(String.class, "equals", methodType(boolean.class, Object.class));
        assert((boolean)equals.bindTo("me").invokeExact((Object) "me"));
        assert(!(boolean)equals.bindTo("me").invokeExact((Object)"thee"));
        assert( (boolean) equals.invokeExact("me", (Object)"me"));
        assert(!(boolean) equals.invokeExact("me", (Object)"thee"));
 
        MethodHandle eq2 = equals.asSpreader(Object[].class, 2);
        assert( (boolean) eq2.invokeExact(new Object[]{ "me", "me" }));
        assert(!(boolean) eq2.invokeExact(new Object[]{ "me", "thee" }));
 
        MethodHandle eq1 = equals.asSpreader(Object[].class, 1);
        assert( (boolean) eq1.invokeExact("me", new Object[]{ "me" }));
        assert(!(boolean) eq1.invokeExact("me", new Object[]{ "thee" }));
 
        //toString方法的参数类型都是数组
        MethodHandle caToString = publicLookup()
                .findStatic(Arrays.class, "toString", methodType(String.class, char[].class));
        assertEquals("[A, B, C]", (String) caToString.invokeExact("ABC".toCharArray()));
        //限定方法的参数个数是3
        MethodHandle caString3 = caToString.asCollector(char[].class, 3);
        assertEquals("[A, B, C]", (String) caString3.invokeExact('A', 'B', 'C'));
       //会把数组元素转换成3个参数
        MethodHandle caToString3 = caString3.asSpreader(char[].class, 3);
        assertEquals("[A, B, C]", (String) caToString3.invokeExact("ABC".toCharArray()));
        //报错数组长度不对 array is not of length 3
//        assertEquals("[A, B, C]", (String) caToString3.invokeExact("ABCD".toCharArray()));
        //会把数组元素转换成2个参数
        MethodHandle caToString2 = caString3.asSpreader(char[].class, 2);
        assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray()));
        //会把数组元素转换成1个参数
        MethodHandle caToString1 = caString3.asSpreader(char[].class, 1);
        assertEquals("[A, B, C]", (String) caToString1.invokeExact('A','B',"C".toCharArray()));
    }
 
    @Test
    public void asFixedArityTest() throws Throwable {
        MethodHandle asListVar = publicLookup()
                .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class))
                .asVarargsCollector(Object[].class);
        //固定参数类型,即不能自动转换
        MethodHandle asListFix = asListVar.asFixedArity();
        assertEquals("[1]", asListVar.invoke(1).toString());
        Exception caught = null;
        try {
            //报错Cannot cast java.lang.Integer to [Ljava.lang.Object;
            asListFix.invoke((Object)1);
        } catch (Exception ex) {
            caught = ex;
            ex.printStackTrace();
        }
        assert(caught instanceof ClassCastException);
        assertEquals("[two, too]", asListVar.invoke("two", "too").toString());
        try {
            //报错cannot convert MethodHandle(Object[])List to (String,String)void
            asListFix.invoke("two", "too");
        } catch (Exception ex) {
            caught = ex;
            ex.printStackTrace();
        }
        assert(caught instanceof WrongMethodTypeException);
        Object[] argv = { "three", "thee", "tee" };
        assertEquals("[three, thee, tee]", asListVar.invoke(argv).toString());
        //必须使用Object[]类型
        assertEquals("[three, thee, tee]", asListFix.invoke(argv).toString());
        assertEquals(1, ((List) asListVar.invoke((Object)argv)).size());
        //将Object[]类型转换成Object时,因为实际类型是Object[]所以没报错
        assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
    }
 
    @Test
    public void invokeWithArgumentsTest() throws Throwable {
        MethodHandles.Lookup lookup=MethodHandles.lookup();
        MethodHandle methodHandle=lookup.findStatic(Arrays.class,"deepToString", methodType(String.class,Object[].class)).asCollector(Object[].class,3);
        //调用参数类型是Object,Object,Object的方法
        String result=(String)methodHandle.invokeWithArguments(1,2,3);
        System.out.println(result);
        result=(String)methodHandle.invokeWithArguments(Arrays.asList(4,5,6));
        System.out.println(result);
        //报错expected (Object,Object,Object)String but found (int,int,int)String
//        result=(String)methodHandle.invokeExact(1,2,3);
        result=(String)methodHandle.invoke(1,2,3);
        System.out.println(result);
     }
 
}

5、MethodHandles
MethodHandles包含一系列的操作MethodHandler的静态方法,可以分为以下3类:

1)创建Lookup实例

Lookup lookup():该方法返回的Lookup实例可以访问所有的类成员,包括私有成员,不过前提是lookup class有访问权限

Lookup publicLookup():该方法返回的Lookup实例只能访问公开的类成员,该实例的lookup class是Object.class

2)数组操作

MethodHandle arrayElementGetter(Class<?> arrayClass):获取读取数组元素的MethodHandle
MethodHandle arrayElementSetter(Class<?> arrayClass):获取修改数组元素的MethodHandle

3)invoker操作

MethodHandle invoker(MethodType type):相当于执行publicLookup().findVirtual(MethodHandle.class, "invoke", type),获取的MethodHandle可以将MethodHandle实例作为接受对象,调用MethodHandle实例绑定的方法,不过调用时需要通过invokeWithArguments方法调用
MethodHandle exactInvoker(MethodType type):相当于执行publicLookup().findVirtual(MethodHandle.class, "invokeExact", type)
MethodHandle spreadInvoker(MethodType type, int leadingArgCount):相当于执行如下代码:

image.png

4)MethodHandle参数处理

MethodHandle explicitCastArguments(MethodHandle target, MethodType newType):将MethodHandle的MethodType转换成新的newType,相当于MethodHandle#asType()方法,不过转换限制更少。
MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder):调整MethodHandle的入参顺序,比如有三个参数,正常按照0,1,2的顺序传参,如果order位2,0,1则invoke调用时第一个入参会放到方法入参的第三个参数,第二个参数放到方法入参的第一个参数,第三个参数放到方法入参的第二个参数。
MethodHandle insertArguments(MethodHandle target, int pos, Object... values):将从索引pos开始的方法入参的值固定为后面的values,invoke时只需要传其他的非固定值的参数即可
MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes):从入参索引pos开始丢弃valueTypes类型的参数,剩下的参数用来调用target。
MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes); 同上
MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters):参数的预处理,从入参索引pos开始的参数都执行一遍后面的filter,执行结果作为新的入参调用target
MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter):同上参数预处理,预处理结果作为新的入参调用target,不过支持嵌套调用
MethodHandle foldArguments(MethodHandle target, MethodHandle combiner):同上参数预处理,不过combiner预处理的结果是作为新的入参插入到原来的入参的前面,最后用这些参数执行target
MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter):先执行target,执行结果作为filter的入参,执行filter的结果作为最后的结果

5)MethodHandle条件调用

MethodHandle guardWithTest(MethodHandle test,MethodHandle target,MethodHandle fallback):先执行test,如果test返回true,则执行target,否则执行fallback
MethodHandle catchException(MethodHandle target,Class<? extends Throwable> exType,MethodHandle handler) :执行target,如果抛出异常exType,则执行handler

6)特殊的MethodHandle
MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType):返回一个将指定类型exType的异常作为入参并抛出该异常的MethodHandle,returnType非空但无意义
MethodHandle identity(Class<?> type):返回一个接受指定类型的一个参数的MethodHandle实例,会将入参直接返回。
MethodHandle constant(Class<?> type, Object value):返回一个没有入参的MethodHandle实例,返回固定的值value,value的类型就是type

测试用例如下:

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.List;

import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodType.methodType;
import static org.junit.Assert.assertEquals;

public class MethodHandlesTest {

   @Test
   public void arrayTest() throws Throwable {
       int[] a={1,2,3};
       //参考源码对应的数组读操作的方法名是getElement
       MethodHandle getter= MethodHandles.arrayElementGetter(a.getClass());
       //通过lookup方法返回的Lookup实例无法查找上述方法
//        MethodHandle getter= MethodHandles.lookup().findStatic(a.getClass(),"getElement", MethodType.methodType(a.getClass().getComponentType(), a.getClass(), int.class));

       int a_0=(int)getter.bindTo(a).invoke(0);
       int a_1=(int)getter.bindTo(a).invoke(1);
       int a_2=(int)getter.bindTo(a).invoke(2);
       System.out.println("a="+a_0+","+a_1+","+a_2);

       //参考源码对应的数组写操作的方法名是setElement
       MethodHandle setter= MethodHandles.arrayElementSetter(a.getClass());
       setter.invoke(a,0,11);
       setter.invoke(a,1,22);
       setter.invoke(a,2,33);
       System.out.println(Arrays.toString(a));

       //Java 反射
       a_0=Array.getInt(a,0);
       a_1=Array.getInt(a,1);
       a_2=Array.getInt(a,2);
       System.out.println("a="+a_0+","+a_1+","+a_2);

       Array.set(a,0,31);
       Array.set(a,1,32);
       Array.set(a,2,33);
       System.out.println(Arrays.toString(a));
   }

   @Test
   public void invokerTest() throws Throwable {
       MethodType methodType= methodType(String.class,String.class,int.class);
       MethodHandle target = publicLookup().findStatic(MethodHandlesTest.class, "invokee",
//                MethodType.genericMethodType(0, true));
               methodType(String.class,String.class,int.class));
       //将MethodHandle按照目标methodType做适配
       target = target.asType(methodType);

       MethodHandle inv=MethodHandles.exactInvoker(methodType);
       //返回的MethodHandle必须通过invokeWithArguments调用,否则各种类型转换报错
       String s=(String)inv.invokeWithArguments(target,"test",1);
       System.out.println(s);

       inv=MethodHandles.invoker(methodType);
       s=(String)inv.invokeWithArguments(target,"test",2);
       System.out.println(s);

       inv=MethodHandles.spreadInvoker(methodType,0);
       s=(String)inv.invokeWithArguments(target,new Object[]{"test",3});
       System.out.println(s);

       inv=MethodHandles.spreadInvoker(methodType,1);
       s=(String)inv.invokeWithArguments(target,"test",new Object[]{4});
       System.out.println(s);
   }

//    public static String invokee(Object... args) {
//        return Arrays.toString(args);
//    }

   public static String invokee(String s,int a) {
       return "s="+s+",a="+a;
   }

   @Test
   public void ArgumentsTest() throws Throwable {
       MethodHandle target = publicLookup().findStatic(MethodHandlesTest.class, "invokee",
               methodType(String.class,String.class,int.class));
       //报错cannot convert MethodHandle(Object,Long)String to (String,int)String
//        MethodHandle newHandle=MethodHandles.explicitCastArguments(target,MethodType.methodType(String.class,Object.class,Long.class));
       //如果执行asType方法就可以转换成目标type则直接使用asType方法完成转换,否则尝试额外的转换
       MethodHandle newHandle=MethodHandles.explicitCastArguments(target, methodType(String.class,Object.class,long.class));
       String result=(String)newHandle.invoke("ss",2);
       System.out.println(result);
   }

   @Test
   public void ArgumentsTest2() throws Throwable {
       MethodType intfn1 = methodType(int.class, int.class);
       MethodType intfn2 = methodType(int.class, int.class, int.class);
       MethodHandle sub = lookup().findStatic(this.getClass(),"sub",intfn2);
       assert(sub.type().equals(intfn2));
       //通过reorder改变实际传递参数的顺序
       MethodHandle sub1 = permuteArguments(sub, intfn2, 0, 1);
       MethodHandle rsub = permuteArguments(sub, intfn2, 1, 0);
       assert((int)rsub.invokeExact(1, 100) == 99);
       assert((int)sub1.invokeExact(1, 100) == -99);
       MethodHandle add = lookup().findStatic(this.getClass(),"add",intfn2);
       assert(add.type().equals(intfn2));
       MethodHandle twice = permuteArguments(add, intfn1, 0, 0);
       assert(twice.type().equals(intfn1));
       assert((int)twice.invokeExact(21) == 42);
   }

   public static int sub(int x,int y){
       return x-y;
   }

   public static int add(int x,int y){
       return x+y;
   }

   public static int add(int x,int y,int z){
       return x+y+z;
   }

   @Test
   public void ArgumentsTest3() throws Throwable {
       MethodHandles.Lookup lookup= lookup();
       MethodType intfn4 = methodType(int.class, int.class, int.class,int.class);
       MethodType intfn3 = methodType(int.class, int.class, int.class);
       MethodType intfn2 = methodType(int.class, int.class);
       MethodHandle add=lookup.findStatic(this.getClass(),"add",intfn4);
       int result=(int)add.invoke(1,2,3);
       System.out.println(result);

       //将4作为参数索引为2的参数的绑定值,即该参数固定为4,前面索引1,2的参数由invoke方法传入
       //使用固定参数后,该参数对应的参数类型从原来的MethodType中去除
       MethodHandle addNew=MethodHandles.insertArguments(add,2,4);
       result=(int)addNew.invoke(1,2);
       System.out.println(result);
       assertEquals(addNew.type(),intfn3);

       addNew=MethodHandles.insertArguments(add,1,5,6);
       result=(int)addNew.invoke(1);
       System.out.println(result);
       assertEquals(addNew.type(),intfn2);
   }

   @Test
   public void ArgumentsTest4() throws Throwable {
       MethodHandle cat = lookup().findVirtual(String.class,
               "concat", methodType(String.class, String.class));
       assertEquals("xy", (String) cat.invokeExact("x", "y"));
       MethodType bigType = cat.type().insertParameterTypes(0, int.class, String.class);
       System.out.println(bigType);
       List<Class<?>> classList=bigType.parameterList().subList(0,2);
       System.out.println(classList);
       //生成的MethodHandle会丢弃掉从参数索引0开始的classList的参数,使用剩余的参数调用原来cat绑定的方法
//        MethodHandle d0 = dropArguments(cat, 0, classList);
       MethodHandle d0 = dropArguments(cat, 0, int.class, String.class);
       //为了跟丢弃的参数适配,生成的MethodHandle的MethodType相当于在原来的MethodType的基础上增加classList对应的参数类型
       assertEquals(bigType, d0.type());
       assertEquals("yz", (String) d0.invokeExact(123, "x", "y", "z"));

       d0 = dropArguments(cat, 1, int.class, String.class);
       assertEquals("xz", (String) d0.invokeExact( "x",123, "y", "z"));
   }

   @Test
   public void ArgumentsTest5() throws Throwable {
       MethodHandle cat = lookup().findVirtual(String.class,
               "concat", methodType(String.class, String.class));
       MethodHandle upcase = lookup().findVirtual(String.class,
               "toUpperCase", methodType(String.class));
       assertEquals("xy", (String) cat.invokeExact("x", "y"));

       //filterArguments添加若干个参数的预处理MethodHandle,cat执行前会使用upcase从指定的参数开始预处理
       MethodHandle f0 = filterArguments(cat, 0, upcase);
       assertEquals("Xy", (String) f0.invokeExact("x", "y")); // Xy

       MethodHandle f1 = filterArguments(cat, 1, upcase);
       assertEquals("xY", (String) f1.invokeExact("x", "y")); // xY

       MethodHandle f2 = filterArguments(cat, 0, upcase, upcase);
       assertEquals("XY", (String) f2.invokeExact("x", "y")); // XY
   }

   @Test
   public void ArgumentsTest6() throws Throwable {
       MethodHandle deepToString = publicLookup()
               .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));

       MethodHandle ts1 = deepToString.asCollector(String[].class, 1);
       assertEquals("[strange]", (String) ts1.invokeExact("strange"));

       MethodHandle ts2 = deepToString.asCollector(String[].class, 2);
       assertEquals("[up, down]", (String) ts2.invokeExact("up", "down"));

       MethodHandle ts3 = deepToString.asCollector(String[].class, 3);
       //从参数索引1的位置开始的参数执行ts2,将执行结果作为参数执行ts3
       MethodHandle ts3_ts2 = collectArguments(ts3, 1, ts2);
       assertEquals("[top, [up, down], strange]",
               (String) ts3_ts2.invokeExact("top", "up", "down", "strange"));

       MethodHandle ts3_ts2_ts1 = collectArguments(ts3_ts2, 3, ts1);
       assertEquals("[top, [up, down], [strange]]",
               (String) ts3_ts2_ts1.invokeExact("top", "up", "down", "strange"));
       //从参数索引1的位置开始的参数执行ts3,将执行结果作为参数执行ts3_ts2
       MethodHandle ts3_ts2_ts3 = collectArguments(ts3_ts2, 1, ts3);
       assertEquals("[top, [[up, down, strange], charm], bottom]",
               (String) ts3_ts2_ts3.invokeExact("top", "up", "down", "strange", "charm", "bottom"));
   }


   @Test
   public void ArgumentsTest7() throws Throwable {
       MethodHandle upcase = lookup().findVirtual(String.class,
               "toUpperCase", methodType(String.class));
       MethodHandle cat = lookup().findVirtual(String.class,
               "concat", methodType(String.class, String.class));
       assertEquals("boojum", (String) cat.invokeExact("boo", "jum"));
       //upcase也是预处理,不同的是预处理的结果会做一个新的参数插入到原来的参数列表的前面,然后执行cat
       MethodHandle catTrace = foldArguments(cat, upcase);
       assertEquals("BOOboo", (String) catTrace.invokeExact("boo"));
   }

   @Test
   public void ArgumentsTest8() throws Throwable {
       MethodHandle cat = lookup().findVirtual(String.class,
               "concat", methodType(String.class, String.class));
       MethodHandle length = lookup().findVirtual(String.class,
               "length", methodType(int.class));
       System.out.println((String) cat.invokeExact("x", "y")); // xy
       //filterReturnValue是后处理,先执行cat,执行的结果作为参数执行length,返回最终的结果
       MethodHandle f0 = filterReturnValue(cat, length);
       System.out.println((int) f0.invokeExact("x", "y")); // 2
   }

   @Test
   public void specialMethodHandleTest() throws Throwable {
       //返回的MethodHandle接收特定类型的异常的实例,然后抛出异常
       MethodHandle methodHandle=MethodHandles.throwException(void.class,UnsupportedOperationException.class);
       methodHandle.invoke(new UnsupportedOperationException());
   }

   @Test
   public void specialMethodHandleTest2() throws Throwable {
       //返回的MethodHandle接收指定类型的唯一一个参数,并将其返回
       MethodHandle methodHandle=MethodHandles.identity(String.class);
       System.out.println((String)methodHandle.invoke("s"));
   }

   @Test
   public void specialMethodHandleTest3() throws Throwable {
       //返回的MethodHandle实例永远返回一个指定类型的固定的值,没有任何入参
       MethodHandle methodHandle=MethodHandles.constant(String.class,"test");
       System.out.println((String)methodHandle.invoke());
       System.out.println((String)methodHandle.invoke());
   }
}

三、与Java Reflect的区别
可以用Java Reflect实现第一个测试用例的效果么?答案是可以,如下:

import java.lang.reflect.Method;
 
class TestA{
    public void println(String s){
        System.out.println("TestA println:"+s);
    }
}
 
class TestB{
    public void println(String s){
        System.out.println("TestB println:"+s);
    }
}
 
public class MethodHandlerTest {
 
 
    public static void reflectTest(Object obj,String s) throws Throwable{
        Method method=obj.getClass().getMethod("println",String.class);
        method.invoke(obj,s);
    }
 
    public static void main(String[] args) throws Throwable{
        String test="Hello World";
 
        reflectTest(new TestA(),test);
        reflectTest(new TestB(),test);
        reflectTest(System.out,test);
 
    }
}

上述示例和之前使用MethodHandle操作字段,普通方法,构造方法及数组的示例中,我们用Java反射的API执行了相同操作,反射的代码明显比MethodHandle更简洁明了,那MethodHandle的价值在哪?

MethodHandle最大的价值在于其更轻量,只包含方法调用必要的信息,是对方法调用句柄而非方法本身的抽象;反射中的Method类是Java方法在Java语言层面的一个完整抽象,包含方法的注解,参数类型,返回类型,修饰符等跟方法调用本身不直接相关的东西。

MethodHandle在执行时不会检查是否具有调用权限,只在MethodHandle创建时检查;而Method类执行的方法调用或者属性操作等是每次执行都会检查是否具有调用权限,因此MethodHandle可以省掉一次调用权限检查的损耗

MethodHandle的底层实现是模拟字节码的行为,而Java反射只是Java层面的层层方法调用,调用链更长,理论上MethodHandle的性能更好。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容