什么是Java中的方法句柄

1.简介


在本教程中,我们将研究Java 7中引入的重要API,并在新版本java.lang.invoke.MethodHandles中进行了扩展。



我们将学习什么是方法句柄,如何创建和使用它们。


2.什么是方法句柄?


在API文档中,方法句柄具有以下定义:


方法句柄是对基本方法,构造函数,字段或其他低级操作的类型化,可执行引用,带有参数或返回值的其他转换。

换句话说,方法句柄是用于查找,调整和调用方法的低级机制。 方法句柄是不可变的,没有显示状态。


要创建和使用MethodHandle您需要执行4个步骤:


  1. 创建搜索描述符-查找
  2. 声明方法类型
  3. 搜索方法句柄
  4. 调用方法句柄

2.1。 方法处理与反射


引入了方法句柄以与java.lang.reflect API一起使用,如 它们是为不同目的而创建的,其特征也不同。


在性能方面, MethodHandles API可能比Reflection API快得多,因为访问检查是在创建时执行的,而不是在执行时执行的 。 如果有安全经理,这种差异会增加,因为 搜索类并获取其元素还需进行其他检查。


但是,性能并不是任务最佳性的唯一指标,必须考虑到,由于缺少诸如获取类方法,检查访问令牌等机制,MethodHandles API更加难以使用。


尽管如此,MethodHandles API仍允许使用递归方法,从而更改参数的类型和顺序。


现在,了解了MethodHandles API的定义和目的之后,我们就可以使用它们。 让我们从寻找方法开始。


3.创建查找


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


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


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


 MethodHandles.Lookup publicLookup = MethodHandles.publicLookup(); 

但是,如果需要访问私有方法和受保护的方法,则可以改用lookup()方法:


 MethodHandles.Lookup lookup = MethodHandles.lookup(); 

4.创建一个MethodType


若要创建MethodHandle,必须将查找对象设置为类型,这可以使用MethodType类来完成。


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


MethodType结构很简单,它由返回类型以及相应数量的参数类型组成,这些参数类型应在方法句柄和调用代码之间完全对应。


与MethodHandle一样,MethodType的所有实例都是不可变的。


让我们看看如何定义将java.util.List类定义为返回类型并将Object数组定义为数据输入类型的MethodType:


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

如果该方法返回简单或空值类型,则使用代表这些类型的类(void.class, int.class …)


定义一个返回一个int并接受一个Object的MethodType:


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

您可以开始创建MethodHandle。


5.搜索方法句柄


设置方法的类型之后,要创建MethodHandle,您需要使用查找对象或publicLookup查找它,该对象还返回源类和方法名称。


查找提供了一组方法,使您可以在考虑方法范围的情况下以最佳方式查找方法句柄。 考虑从最简单的方法开始的基本方法。


5.1。 方法的方法句柄


使用findVirtual()方法,可以为实例方法创建MethodHandle。 基于String类的concat()方法创建它:


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

5.2。 静态方法的方法句柄


要访问静态方法,可以使用findStatic()方法:


 MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt); 

在这种情况下,我们为将Object类型的数组转换为List的方法创建了一个方法句柄。


5.3。 构造函数的方法句柄


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


使用String参数创建一个行为类似于Integer类的构造函数的方法句柄:


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

5.4。 字段的方法句柄


使用方法句柄,您还可以访问字段。


让我们从定义Book类开始:


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

作为初始条件,我们可以在方法句柄和声明的属性之间直接看到,因此我们可以创建行为类似于get方法的方法句柄:


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

有关变量/字段管理的更多信息,请参阅Java 9 Variable Handles Demystified文章,其中我们讨论了Java 9中引入的java.lang.invoke.VarHandle API


5.5。 私有方法的方法句柄


您可以使用java.lang.reflect API为private类型的方法创建方法句柄。
让我们开始为Book类创建一个私有方法:


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

现在,我们可以使用formatBook()方法的行为来创建方法句柄:


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

6.调用方法句柄


创建方法句柄后,请继续执行下一步。 MethodHandle类为我们提供了三种调用方法句柄的方法: invoke()invokeWithArugments()invokeExact()


让我们从invoke方法开始。


6.1。 调用方法句柄


当使用invoke()方法时,参数的数量(arity)是固定的,但是可以执行类型转换以及对返回值的参数和类型进行打包/拆包。


现在让我们看看如何将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参数之前先对其进行解压缩。


6.2。 带参数调用


使用invokeWithArguments调用方法句柄的限制最少。


实际上,除了检查类型以及打包/拆包参数和返回值外,它还允许您使用可变数量的参数进行调用。


实际上,我们可以创建一个整数列表,其中包含长度未知的int值数组:


 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)); 

6.3。 准确通话


如果我们需要更严格地执行方法句柄(根据参数集及其类型),请使用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); 

在这种情况下,如果我们将一个不是int的数字传递给invokeExact方法,则在调用时,我们会得到WrongMethodTypeException


7.处理数组


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" })); 

8.澄清方法句柄


设置方法句柄后,您可以通过绑定到参数来优化它,而无需调用该方法。


例如,在Java 9中,此技巧用于优化字符串连接。


让我们看看如何通过将后缀附加到concatMH来进行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!")); 

9. Java 9更新


Java 9对MethodHandles API进行了一些更改,以使其更易于使用。


更新涉及3个主要方面:


  • 查找功能 -允许从不同的上下文中搜索并支持接口中的非抽象方法。
  • 带参数的操作 -改善折叠,收集和分配参数的功能。
  • 其他组合包括添加循环操作( loopwhileLoopdoWhileLoop等),并使用tryFinally改进了异常管理。

这些变化带来了其他有用的创新:


  • 改进的JVM编译器优化
  • 实例减少
  • 具体使用MethodHandles API

Javadoc MethodHandles API中提供了更详细的更改列表。


10.结论


在本文中,我们了解了MethodHandles API,还了解了什么是Method Handles以及如何使用它们。


我们还描述了它如何与反射API相关联。 由于调用方法句柄是相当底层的操作,因此只有在它们完全适合您的任务时才有理由使用它们。


和往常一样,该文章的所有源代码都可以在Github上获得

Source: https://habr.com/ru/post/zh-CN431922/


All Articles