美文网首页
MethodHandle

MethodHandle

作者: 睦月MTK | 来源:发表于2020-05-07 17:25 被阅读0次

    声明:本文是基于Java8的Api来进行叙述的,Java8之后添加的内容将不会涉及。不足之处,望不吝指点


    一、MethodHandle概述
    • Q:MethodHandle是什么?
      A:方法句柄类似于反射框架中的Method,只不过其功能更为强大、效率也更高
    • Q:MethodHandle比起反射框架中的对应类有什么不同?
      A:(1)表示范围更广。MethodHandle不仅仅只是能够代表Method,它更像是反射框架中MethodFieldConstructor三个类的抽象整合。(2)效率更高。MethodHandle在方法的执行过程中将不会和反射一样会去检查访问权限之类的东西。(3)更加安全。方法查找阶段将会严格按照访问控制权限来执行,并不像反射一样可以访问控制权限之外的东西。(4)扩展性强。方法句柄能够对执行时候的参数、返回值、异常等部分进行扩展,比如目标方法需要2个参数,但是我想在实际使用时只想让使用者输入1个参数(也就是说我会默认提供一个参数),这就可以通过方法句柄的转换来完成。(5)可以和反射框架协同使用。

    二、创建MethodHandle的三个基本步骤
    1. 方法类型的确定
    2. 获取方法查找的客户端
    3. 使用方法查找的客户端查找指定方法类型、名字、访问权限等等的方法,成功后返回代表对应的方法的方法句柄(MethodHandle)
    • 以创建Math#max方法的句柄为例:
    //创建一个参数类型为`int,int`,返回值类型为`int`的方法类型实例
    MethodType type = MethodType.fromMethodDescriptorString("(II)I",null);
    //获取方法查找的客户端
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    //通过该客户端查找`Math`类中名字为`max`方法类型为type的静态方法
    MethodHandle maxHandle = lookup .findStatic(Math.class , "max" , type);
    

    三、MethodType详解
    • 创建MethodType
      • MethodType#genericMethodType
        该方法会创建一个参数值及返回值类型都是Object的特殊方法类型,使用时只需要指定参数个数,以及最后一个参数是否是Object[]的参数两个部分就可以了
      • MethodType#methodType
        该方法显示指定参数类型及返回值类型,第一个参数用于指定方法的返回值类型,如果是表示静态方法的,第一个可以不填。后面的参数用于指定表示方法的参数类型,接受不定长参数
      • MethodType#fromMethodDescriptorString
        该方法接受一个代表方法返回值类型和参数类型的字符串,将会解析这个字符串并生成对应的方法类型实例,第二个参数可以为null
    • 修改MethodType
      • 修改参数列表
        • 增加参数
          • appendParameterTypes在参数列表的末尾再添加一系列参数
          • insertParameterTypes在指定位置处添加一系列参数
        • 修改参数类型
          • changeParameterType修改指定位置处的参数类型
        • 删除参数
          • dropParameterTypes删除指定位置到指定位置处的参数
      • 修改返回值类型
        • changeReturnType
      • 类型擦除
        • erase将所有引用类型都转换为Object类型
        • generic将所有引用类型和基本类型都转换为Object类型
      • 装箱类型的变换
        • wrap将所有基础类型转换为对应的装箱类型
        • unwrap将所有的装箱类型转换为对应的基础类型
    • 其他方法
      • parameterArray/parameterList/parameterType将参数类型列表用数组返回 / 将参数类型列表用List返回 / 返回指定位置处的参数类型
      • parameterCount参数个数
      • returnType返回值类型
      • toMethodDescriptorString返回对应的字符串表示方式
      • hasPrimitives/hasWrappers是否有基本类型 / 是否有装箱类型

    四、MethodHandles.Lookup详解
    • 查找构造方法
      • findConstructor找寻指定类中符合指定类型(返回值设置为void.class)的构造方法
    • 查找域(不是真的查找对应域Setter和Getter方法,是直接对域进行赋值的,查找域时直接填入域的类型就可以了,不用MethodType
      • findGetter/findSetter
      • findStaticGetter/findStaticSetter
    • 查找方法
      • findVirtual查找非静态方法
      • bindfindVirtual上事先还绑定了方法的执行者
      • findSpecial查找非公开类型的非静态方法,参数的最后需要指定调用者的类型,该调用者的类型应当是使用该方法返回的句柄进行调用的那个类,并且该类有权限能够访问指定的方法,否则前者将在执行时报出调用者与实际不符的错误,后者将在查找时报出没有指定方法的错误
      • findStatic 与前三个不同,这是专门用来查找静态方法的
    • 反射转换为句柄
      • unreflectConstructor将反射的Constructor转换为句柄
      • unreflectGetter将反射的Field转换为句柄
      • unreflectSetter将反射的Field转换为句柄
      • unreflect将反射的Method转换为句柄
      • unreflectSpecial将反射的特殊访问权限的Method转换为句柄

    五、MethodHandle详解
    • 句柄的调用
      • invokeExact严格检查实际参数类型和返回值类型是否和句柄描述一致的调用,比如句柄描述中有一个参数类型为Object,则调用时必须传入一个Object类型的参数。又如System.out.println接受Object类型的参数,而你的句柄返回类型是String类型,你想打印这个字符串,然而实际上会报错,除非你句柄的返回类型也是Object类型的,再,如果你想直接单独使用(直接调用)也不行,因为那意味着你的句柄返回类型应当为void.class
      • invoke内嵌了自动类型转换的调用,会根据返回类型,参数类型、长度自动适配,极其方便
    • 其他方法
      • bindTo预先绑定句柄的第一个参数,对于非静态方法来说就是绑定调用者
      • asType接受一个MethodType实例,尝试将句柄的方法类型改为指定类型,注意,只是尝试转换,也就是说需要符合一定规则,比如把String类型的返回值改为Object类型
      MethodType methodType = MethodType.fromMethodDescriptorString("(II)Ljava/lang/String;" , null);
      MethodHandle mh = MethodHandles.lookup().findVirtual(String.class , "substring" , methodType);
      //由于System.out.println只接受Object类型的参数,故使用asType进行转换
      mh = mh.asType(MethodType.fromMethodDescriptorString("(Ljava/lang/String;II)Ljava/lang/Object;" , null));
      System.out.println(mh.invokeExact("hello" , 1 , 3));
      
      • type返回句柄的方法类型,注意该方法类型对于非静态方法来说,第二个参数会是调用者的类型,与查找方法时的方法类型不同,后者只包含返回值类型、参数类型

    六、MethodHandles详解
    • 特殊句柄
      • 操纵数组元素的句柄:MethodHandles#arrayElementSetterMethodHandles#arrayElementGetter
      int[] arr = new int[]{1,2,3,4,5,6};
      MethodHandle arrHandle = MethodHandles.arrayElementSetter(int[].class);
      arrHandle = arrHandle.bindTo(arr);
      //将index为2的数组元素改为9
      arrHandle.invoke(2,9);
      for(int item : arr){
          System.out.println(item);
      }
      //结果:1 2 9 4 5 6
      
      • 输出等于输入的句柄:MethodHandles#identity该方法接受一个类型参数,生成的方法句柄的返回值和参数(唯一)也会是这个类型,调用时输入一个值,返回值同样会是这个值
      • 输出值固定的句柄:MethodHandles#constant该方法接受一个类型参数和一个输出值,该类型参数是返回值的类型,输出值将会作为每次调用的返回值
    • 句柄转换
      • 为原句柄添加无效的参数的转换:MethodHandles#dropArguments,第一个参数为待转换的句柄,第二参数为要添加参数的位置,第三个参数为要添加的参数类型列表。无效参数的意思时,在真正调用的时候,这些参数会被忽略掉,可以理解为占位符
      MethodHandle dropArgHandle = MethodHandles.lookup().findVirtual(String.class , "toString" , MethodType.fromMethodDescriptorString("()Ljava/lang/String;" , null));
      dropArgHandle = MethodHandles.dropArguments(dropArgHandle , 0 , String.class , int.class);
      System.out.println(dropArgHandle.invoke("123" , 2 ,"hello"));
      //结果:hello
      
      • 为原句柄添加预先绑定的参数值的转换:第一个参数为待转换的句柄,第二个参数为要开始绑定参数的位置,第三个需要被绑定的参数值列表。相当于加强版的bindTo
      MethodHandle bindArgHandle = MethodHandles.lookup().findVirtual(String.class , "concat" , MethodType.fromMethodDescriptorString("(Ljava/lang/String;)Ljava/lang/String;" , null));
      bindArgHandle = MethodHandles.insertArguments(bindArgHandle , 1 , "654");
      System.out.println(bindArgHandle.invoke("123"));
      //结果:123654
      
      • 为参数/返回值添加过滤机制的转换:MethodHandles#filterArguments/MethodHandles#filterReturnValue,前者接受三个参数,第一个参数是原句柄,第二个参数是需要添加过滤机制的位置,第三个参数是用于过滤的方法句柄。后者接受两个参数,第一个参数是原句柄,第二个参数是用于过滤的方法句柄。过滤的方法句柄应该满足这些原则:参数过滤的句柄的返回值类型应该和被过滤的参数类型一致,但是输入类型可以随意,返回值过滤的句柄的输入类型应当和被过滤的返回类型一致,但是输出类型可以随意。(参数->过滤参数句柄->过滤后的参数->原方法句柄->返回值->过滤返回值句柄->过滤后的返回值)
      public class Test {
          private int fieldInt = 2;
          public Test(int fieldInt){
              this.fieldInt = fieldInt;
          }
          public int getSum(int otherInt){
              return fieldInt + otherInt;
          }
      }
      //................................
      MethodHandle getSumHandle = MethodHandles.lookup().findVirtual(Test.class , "getSum" , MethodType.fromMethodDescriptorString("(I)I" , null));
      getSumHandle = getSumHandle.bindTo(new Test(2));
      MethodHandle filterLengthHandle = MethodHandles.lookup().findVirtual(String.class , "length" , MethodType.fromMethodDescriptorString("()I" , null));
      getSumHandle = MethodHandles.filterArguments(getSumHandle , 0 , filterLengthHandle);
      MethodHandle valueOfHandle = MethodHandles.lookup().findStatic(String.class , "valueOf" , MethodType.fromMethodDescriptorString("(I)Ljava/lang/String;" , null));
      getSumHandle = MethodHandles.filterReturnValue(getSumHandle , valueOfHandle);
      String result = (String) getSumHandle.invoke("123456789");
      //(参数类型String->过滤参数句柄(String->int)->过滤后的参数int->原方法句柄(int->int)->返回值int->过滤返回值句柄(int->String)->过滤后的返回值String
      System.out.println(result);
      //结果:11
      
      • 将多个参数整合成一个新参数的句柄转换:MethodHandles#foldArguments,该方法第一个参数为待转换的句柄,第二个参数是从哪个位置开始取多个参数(至于具体取几个参数,由第三个参数方法句柄的参数列表长度决定),第三个参数是来执行该转换的句柄,该句柄的输出值将会添加在第二个参数指定的位置。(参数列表【参数类型1,参数类型2,参数类型3】-> 转换句柄【(参数类型1,参数类型2)-> 参数类型4】-> 转换后参数列表【参数类型4,参数类型1,参数类型2,参数类型3】->原句柄执行)
      public class Test {
          private int fieldInt = 2;
          public Test(int fieldInt){
              this.fieldInt = fieldInt;
          }
          public int getSumFromTwoArgs(int first , int second){
              return first + second + fieldInt;
          }
      }
      //.............................
      MethodHandle getSumPlusHandle = MethodHandles.lookup().findVirtual(Test.class , "getSumFromTwoArgs" , MethodType.fromMethodDescriptorString("(II)I",null));
      getSumPlusHandle = getSumPlusHandle.bindTo(new Test(2));
      MethodHandle filterMaxlengthHandle = MethodHandles.lookup().findStatic(Main.class , "maxLength" , MethodType.fromMethodDescriptorString("(Ljava/lang/String;Ljava/lang/String;)I" , null));
      getSumPlusHandle = MethodHandles.dropArguments(getSumPlusHandle , 1 , String.class , String.class);
      //参数列表(int,String,String,int)  
      getSumPlusHandle = MethodHandles.foldArguments(getSumPlusHandle , 0 ,filterMaxlengthHandle);
      System.out.println(getSumPlusHandle.invoke("12332112" , "2358646" , 6));
      //结果:16
      
      • 将参数顺序重新排列的句柄转换:MethodHandles#permuteArguments,第一个参数是待转换的句柄,第二个参数是顺序变换后的方法类型,第三个参数接受句柄参数多个的整型值,该值表示其所代表的参数在重新排列后应当处于什么位置。详情看示例:
      MethodType oldType = MethodType.fromMethodDescriptorString("(Ljava/lang/String;I)I" , null);
      MethodHandle indexOfHandle = MethodHandles.lookup().findVirtual(String.class , "indexOf" , oldType);
      MethodType newType = MethodType.fromMethodDescriptorString("(Ljava/lang/String;ILjava/lang/String;)I" , null);
      indexOfHandle = MethodHandles.permuteArguments(indexOfHandle , newType , 0 , 2 , 1);
      //(String,String,int)->(String,int,String)
      System.out.println(indexOfHandle.invoke("hello ele" , 2 , "e"));
      //结果:6
      
      • 添加对异常进行处理的句柄转换:Methodhandles#catchException,第一个参数是待处理的句柄,第二个参数是要处理的异常类型,第三个参数是处理异常的句柄,该处理异常的句柄的参数应当为(Exception,String),返回值应当和原句柄的返回值的类型一致。也就是说当异常发生后,该异常会被异常处理句柄处理,然后异常处理句柄的返回值将作为原句柄的返回值被返回
      public class Main {
          public static void main(String[] args) throws Throwable {
              MethodHandle parseIntHandle =   MethodHandles.lookup().findStatic(Integer.class , "parseInt" , MethodType.fromMethodDescriptorString("(Ljava/lang/String;)I" , null));
              MethodHandle exceptionHandle = MethodHandles.lookup().findStatic(Main.class , "exceptionHandle" , MethodType.fromMethodDescriptorString("(Ljava/lang/Exception;Ljava/lang/String;)I" , null));
              parseIntHandle = MethodHandles.catchException(parseIntHandle , Exception.class , exceptionHandle);
              parseIntHandle.invoke("0sad");
          }
          
          public static int exceptionHandle(Exception e , String str){
              System.out.println(e.getMessage());
              return 0;
          }
      }
      //结果:For input string: "0sad"
      
      • 为句柄添加条件执行能力:MethodHandles#guardWithTest,第一个参数是条件句柄,该句柄参数列表应当为空,返回值类型应当为boolean,第二个参数是当条件为真时执行的句柄,第三个参数是当条件为假时执行的句柄,第二、三个句柄应当具有相同的方法类型。句柄执行时,会先执行条件句柄,然后根据结果选择对应的执行句柄去真正执行。
      MethodHandle boolHandle = MethodHandles.constant(boolean.class , true);
      MethodType type = MethodType.fromMethodDescriptorString("(II)I" , null);
      MethodHandle maxHandle = MethodHandles.lookup().findStatic(Integer.class , "max" , type);
      MethodHandle minHandle = MethodHandles.lookup().findStatic(Integer.class , "min" , type);
      boolHandle =MethodHandles.guardWithTest(boolHandle , maxHandle , minHandle);
      System.out.println(boolHandle.invoke(7,9));
      //结果:9
      
    • 句柄模板
      MethodHandles#invoker,句柄模板用于解决这么一种问题:假设现在有10个MethodType一样的方法句柄,我想对这10个方法句柄都添加一个返回值过滤的机制,那么我得转换10次。而有了模板句柄之后,我们可以这么做,创建一个模板句柄,MethodType与待转换的句柄们一致,使用该模板句柄添加一个返回值过滤的机制,然后在使用时指定哪个句柄进行执行就可以了。
    MethodType type = MethodType.fromMethodDescriptorString("(II)I",null);
    MethodHandle maxHandle = MethodHandles.lookup().findStatic(Math.class , "max" , type);
    MethodHandle minHandle = MethodHandles.lookup().findStatic(Math.class , "min" , type);
    MethodHandle templateInvoker = MethodHandles.invoker(type);
    MethodHandle intToStringFilter = MethodHandles.lookup().findStatic(String.class , "valueOf" , MethodType.methodType(String.class , int.class));
    templateInvoker = MethodHandles.filterReturnValue(templateInvoker , intToStringFilter);
    String result = (String) templateInvoker.invoke(maxHandle , 56 ,78);
    System.out.println(result);
    //结果:78
    

    七、其他
    • 接口代理
      MethodHandleProxies#asInterfaceInstance,接口代理就是使用指定句柄来对某个接口的方法进行实现,限制较多,该接口必须只含有一个方法,且用于实现该方法的句柄的参数类型与返回值类型应当与被实现的方法一致
    MethodHandle handle = MethodHandles.lookup().findStatic(Main.class , "printOk" , MethodType.fromMethodDescriptorString("()V" , null));
            Callable runnableProxy = MethodHandleProxies.asInterfaceInstance(Callable.class , handle);
            runnableProxy.call();
    //结果:ok
    
    • 交换点SwitchPoint
      就是使用了SwitchPoint的状态来进行判断的guardWithTest
    MethodType type = MethodType.fromMethodDescriptorString("(II)I",null);
    MethodHandle maxHandle = MethodHandles.lookup().findStatic(Math.class , "max" , type);
    MethodHandle minHandle = MethodHandles.lookup().findStatic(Math.class , "min" , type);
    SwitchPoint switchPoint = new SwitchPoint();
    MethodHandle ultimateHandle = switchPoint.guardWithTest(maxHandle , minHandle);
    System.out.println(ultimateHandle.invoke(1,10));
    SwitchPoint.invalidateAll(new SwitchPoint[]{switchPoint});
    System.out.println(ultimateHandle.invoke(1,10));
    //结果:10 1
    

    参考文档:
    [1] [深入理解Java7:核心技术与最佳实践]

    相关文章

      网友评论

          本文标题:MethodHandle

          本文链接:https://www.haomeiwen.com/subject/idqoghtx.html