开发者

Java MethodHandles介绍与反射对比区别详解

目录
  • 前言
  • 什么是方法句柄?
    • 方法句柄与反射
  • 创建Lookup
    • 创建MethodType
      • 找到方法句柄
        • 方法的MethodHandle
        • 静态方法的方法句柄
        • 构造函数的方法句柄
        • 字段的方法句柄
        • 私有方法的方法句柄
      • 调用方法句柄
        • 使用参数调用
        • 调用Exact
      • 使用数组
        • 增强方法句柄
          • Java 9增强功能
            • 结论

              前言

              在本文中,我们将探讨一个重要的API,它是在Java7中引入的,并在以后的jdk版本中得到了增强,即java.lang.invoke.MethodHandles

              特别是,我们将学习什么是方法句柄(method handles),如何创建它们以及如何使用它们。

              什么是方法句柄?

              如API文件中所述,关于其定义:

              方法句柄是对基础方法、构造函数、字段或类似低级操作的类型化、直接可执行的引用,具有参数或返回值的可选转换。

              更简单地说,方法句柄是一种用于查找、调整和调用方法的低级机制

              方法句柄是不可变的,并且没有可见的状态。

              要创建和使用MethodHandle,需要4个步骤:

              • 创建lookup
              • 创建method type
              • 查找方法句柄
              • 调用方法句柄

              方法句柄与反射

              引入方法句柄是为了与现有的java.lang.reflect API一起工作,因为它们具有不同的用途和不同的特性。

              从性能角度来看,MethodHandles API可能比Reflection API快得多,因为访问检查是在创建时而不是在执行时进行的。如果存在安全管理器,则这种差异会被放大,因为成员和类查找要接受额外的检查。

              然而,考虑到性能并不是任务的唯一适用性度量,我们还必须考虑到,由于缺乏成员类枚举、可访问性标志检查等机制,MethodHandles API更难使用。

              即便如此,MethodHandles API提供了柯里化方法、更改参数类型和更改其顺序的可能性php

              有了MethodHandles API的清晰定义和目标,我们现在可以从lookup开始使用它们。

              创建Lookup

              当我们想要创建方法句柄时,要做的第一件事是检索查找Lookup,即负责为查找类可见的方法、构造函数和字段创建方法句柄的工厂对象。

              通过MethodHandles API,可以创建具有不同访问模式的查找对象。

              让我们创建一个提供对公共方法访问的查找:

              MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();

              然而,如果我们也想访问私有和受保护的方法,我们可以使用lookup()方法:

              MethodHandles.Lookup lookup = MethodHandles.lookup();

              创建MethodType

              为了能够创建MethodHandle,查找对象需要其类型的定义,这是通过MethodType类实现的。

              特别是,MethodType表示方法句柄接受和返回的参数和返回类型,或方法句柄调用程序传递和期望的参数和返回类型。

              MethodType的结构很简单,它由一个返回类型和适当数量的参数类型组成,这些参数类型必须在方法句柄及其所有调用方之间正确匹配。

              与MethodHandle相同,即使是MethodType的实例也是不可变的。

              让我们看看如何定义一个MethodType,该MethodType将java.util.List类指定为返回类型,将Object数组指定为输入类型:

              MethodType mt = MethodType.methodType(List.class, Object[].class);

              如果该方法返回基本类型或void作为其返回类型,我们将使用表示这些类型的类(void.class、int.class…)

              让我们定义一个返回int值并接受Object的MethodType:

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

              我们现在可以继续创建MethodHandle。

              找到方法句柄

              一旦我们定义了方法类型,为了创建MethodHandle,我们必须通过lookuppublicLookup对象找到它,同时编程提供原始类和方法名称。

              特别是,查找工厂提供了一组方法,使我们能够在考虑方法范围的情况下以适当的方式找到方法句柄。从最简单的场景开始,让我们探究主要的场景。

              方法的MethodHandle

              使用findVirtual()方法可以为对象方法创建一个MethodHandle。让我们根据String类的concat()方法创建一个:

              MethodType mt = MethodType.methodType(String.class, String.class);
              MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);

              静态方法的方法句柄

              当我们想要访问静态方法时,我们可以使用findStatic()方法:

              Methttp://www.devze.comhodType mt = MethodType.methodType(List.class, Object[].class);
              
              MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt);

              在本例中,我们创建了一个方法句柄,用于将对象数组转换为对象列表。

              构造函数的方法句柄

              可以使用findConstructor()方法访问构造函数。

              让我们创建一个方法句柄,它充当Integer类的构造函数,接受String属性:

              MethodType mt = MethodType.methodType(void.class, String.class);
              
              MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt);

              字段的方法句柄

              使用方法句柄也可以访问字段。

              让我们开始定义Book类:

              public class Book {
                  
                  String id;
                  String title;
              
                  // constructor
              
              }

              先决条件是方法句柄和声明的属性之间具有直接访问可见性,我们可以创建一个充当getter的方法句柄:

              MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class);

              有关处理变量/字段的更多信息,请参阅Java 9 Variable Handles:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/...

              私有方法的方法句柄

              java.lang.reflect API的帮助下,可以为私有方法创建方法句柄。

              让我们开始向Book类添加一个私有方法:

              private String formatBook() {
                  return id + " > " + title;
              }

              现在,我们可以创建一个与formatBook()方法完全相同的方法句柄:

              Method formatBookMethod = Book.class.getDeclaredMethod("fULNybcormatBook");
              formatBookMethod.setAccessible(true);
              
              MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);

              调用方法句柄

              一旦我们创建了方法句柄,下一步就是使用它们。特别是,MethodHandle类提供了3种不同的方法来执行方法句柄:invoke()invokeWithAruments()invokeExact()

              让我们从invoke选项开始。

              当使用invoke()方法时,我们强制要固定的参数数量,但我们允许执行参数和返回类型的强制转换和装箱/取拆箱。

              让我们看看如何使用带框参数的invoke()

              MethodType mt = MethodType.methodType(String.class, char.class, char.class);
              MethodHandle replaceMH = publicLookup.findVirtual(String.class, "replace", mt);
              
              String output = (String) replaceMH.invoke("jovo", Character.valueOf('o'), 'a');
              
              assertEquals("java", output);

              在这种情况下,replaceMH需要char参数,但invoke()在执行之前会对Character参数执行开箱操作。

              使用参数调用

              使用invokeWithArguments方法调用方法句柄是三个选项中限制最小的一个。

              事实上,除了参数和返回类型的强制转换和装箱/取消装箱外,它还允许变量arity调用。

              在实践中,这允许我们从一个int值数组开始创建一个Integer列表:

              MethodType mt = MethodType.methodType(List.class, Object[].class);
              MethodHandle asList = publicLookup.findStatic(Arrays.class, "asList", mt);
              
              List<Integer> list = (List<Integer>) asList.invokeWithArguments(1,2);
              
              assertThat(Arrays.asList(1,2), is(list));

              调用Exact

              如果我们想在执行方法句柄的方式上更加严格(参数的数量及其类型),我们必须使用invokeExact()方法。

              事实上,它没有为所提供的类提供任何类型转换,并且需要固定数量的参数。

              让我们看看如何使用方法句柄对两个int值求和:

              MethodType mt = MethodType.methodType(int.class, int.class, int.class);
              MethodHandle sumMH = lookup.findStatic(Integer.class, "sum", mt);
              
              int sum = (int) sumMH.invokeExact(1, 11);
              
              assertEquals(12, sum);

              如果在这种情况下,我们决定向invokeExact方法传递一个不是int的数字,那么调用将导致WrongMethodTypeException

              使用数组

              MethodHandles不仅用于字段或对象,还用于数组。事实上,使用asSpreader()API,可以生成一个数组扩展方法句柄。

              在这种情况下,方法句柄接受一个数组参数,将其元素扩展为位置参数,并可以选择数组的长度。

              让我们看看如何扩展方法句柄来检查数组中的元素是否相等:

              MethodType mt = MethodType.methodType(boolean.class, Object.class);
              MethodHandle equals = publicLookup.findVirtual(String.class, "equals", mt);
              
              MethodHandle methodHandle = equals.asSpreader(Object[].class, 2);
              
              assertTrue((boolean) methodHandle.invoke(new Object[] { "java", "java" }));

              增强方法句柄

              一旦我们定义了一个方法句柄,就可以通过将方法句柄绑定到一个参数来增强它,而无需实际调用它。

              例如,在Java9中,这种行为用于优化字符串连接。

              让我们看看如何执行串联,将后缀绑定到我们的concatMH

              MethodType mt = MethodType.methodType(String.class, String.class);
              MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);编程客栈
              
              MethodHandle bindedConcatMH = concatMH.bindTo("Hello ");
              
              assertEquals("Hello World!", bindedConcatMH.invoke("World!"));

              Java 9增强功能

              在Java9中,对MethodHandles API进行了一些增强,目的是使其更易于使用。

              这些增强影响了3个主要主题:

              • 查找函数–允许从不同上下文中查找类,并支持接口中的非抽象方法
              • 参数处理——改进参数折叠、参数收集和参数传播功能
              • 附加组合–添加循环(loopwhileLoopdoWhileLoop…),并通过tryFinally提供更好的异常处理支持

              这些变化带来了一些额外的好处:

              • 增加JVM编译器优化
              • 实例化减少
              • 在使用MethodHandles API时启用了精度

              结论

              在本文中,我们介绍了MethodHandles API、它们是什么以及如何使用它们。

              我们还讨论了它与反射API的关系,由于方法句柄允许低级别操作,因此最好避免使用它们,除非它们完全适合工作范围。

              以上就是Java MethodHandles介绍与反射对比区别详解的详细内容,更多关于Java MethodHandles对比反射的资料请关注编程客栈(www.devze.com)其它相关文章!

              0

              上一篇:

              下一篇:

              精彩评论

              暂无评论...
              验证码 换一张
              取 消

              最新开发

              开发排行榜