Method Handles in Java – Java中的方法处理

最后修改: 2018年 3月 2日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this article, we’re going to explore an important API that was introduced in Java 7 and enhanced in the following versions, the java.lang.invoke.MethodHandles.

在这篇文章中,我们将探讨一个重要的API,它在Java 7中被引入,并在随后的版本中得到增强,即 java.lang.invoke.MethodHandles

In particular, we’ll learn what method handles are, how to create them and how to use them.

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

2. What Are Method Handles?

2.什么是方法柄?

Coming to its definition, as stated in the API documentation:

来看它的定义,正如API文档中所述。

A method handle is a typed, directly executable reference to an underlying method, constructor, field, or similar low-level operation, with optional transformations of arguments or return values.

方法句柄是一个类型化的、可直接执行的对底层方法、构造函数、字段或类似底层操作的引用,参数或返回值的转换是可选的。

In a simpler way, method handles are a low-level mechanism for finding, adapting and invoking methods.

以一种更简单的方式,方法句柄是一种寻找、适应和调用方法的低级机制

Method handles are immutable and have no visible state.

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

For creating and using a MethodHandle, 4 steps are required:

对于创建和使用一个MethodHandle,需要4个步骤。

  • Creating the lookup
  • Creating the method type
  • Finding the method handle
  • Invoking the method handle

2.1. Method Handles vs Reflection

2.1.方法处理与反思

Method handles were introduced in order to work alongside the existing java.lang.reflect API, as they serve different purposes and have different characteristics.

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

From a performance standpoint, the MethodHandles API can be much faster than the Reflection API since the access checks are made at creation time rather than at execution time. This difference gets amplified if a security manager is present, since member and class lookups are subject to additional checks.

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

However, considering that performance isn’t the only suitability measure for a task, we have also to consider that the MethodHandles API is harder to use due to the lack of mechanisms such as member class enumeration, accessibility flags inspection and more.

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

Even so, the MethodHandles API offers the possibility to curry methods, change the types of parameters and change their order.

即便如此,MethodHandles API提供了咖喱方法、改变参数类型和改变其顺序的可能性。

Having a clear definition and goals of the MethodHandles API, we can now begin to work with them, starting from the lookup.

在明确了MethodHandles API的定义和目标后,我们现在可以开始使用它们,从查找开始。

3. Creating the Lookup

3.创建查询

The first thing to do when we want to create a method handle is to retrieve the lookup, the factory object that is responsible for creating method handles for methods, constructors, and fields, that are visible to the lookup class.

当我们想创建一个方法句柄时,首先要做的是检索lookup,即负责为方法、构造函数和字段创建方法句柄的工厂对象,这对lookup类来说是可见的。

Through the MethodHandles API, it’s possible to create the lookup object, with different access modes.

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

Let’s create the lookup that provides access to public methods:

让我们创建提供对public方法访问的查询。

MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();

However, in case we want to have access also to private and protected methods, we can use, instead, the lookup() method:

然而,如果我们想访问privateprotected方法,我们可以使用lookup()方法来代替。

MethodHandles.Lookup lookup = MethodHandles.lookup();

4. Creating a MethodType

4.创建一个MethodType

In order to be able to create the MethodHandle, the lookup object requires a definition of its type and this is achieved through the MethodType class.

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

In particular, a MethodType represents the arguments and return type accepted and returned by a method handle or passed and expected by a method handle caller.

特别是,一个MethodType代表了一个方法句柄所接受和返回的参数和返回类型,或者方法句柄调用者所传递和期望的参数和返回类型

The structure of a MethodType is simple and it’s formed by a return type together with an appropriate number of parameter types that must be properly matched between a method handle and all its callers.

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

In the same way as MethodHandle, even the instances of a MethodType are immutable.

MethodHandle一样,即使是MethodType的实例也是不可改变的。

Let’s see how it’s possible to define a MethodType that specifies a java.util.List class as return type and an Object array as input type:

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

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

In case the method returns a primitive type or void as its return type, we will use the class representing those types (void.class, int.class …).

如果该方法返回原始类型或void作为其返回类型,我们将使用代表这些类型的类(void.class, int.class …)。

Let’s define a MethodType that returns an int value and accepts an Object:

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

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

We can now proceed to create MethodHandle.

我们现在可以继续创建MethodHandle

5. Finding a MethodHandle

5.找到一个MethodHandle

Once we’ve defined our method type, in order to create a MethodHandle, we have to find it through the lookup or publicLookup object, providing also the origin class and the method name.

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

In particular, the lookup factory provides a set of methods that allow us to find the method handle in an appropriate way considering the scope of our method. Starting with the simplest scenario, let’s explore the principal ones.

特别是,查找工厂提供了一个方法集,允许我们考虑到我们方法的范围,以适当的方式找到方法柄。从最简单的情况开始,让我们探讨一下主要的情况。

5.1. Method Handle for Methods

5.1.方法的处理方式

Using the findVirtual() method allow us to create a MethodHandle for an object method. Let’s create one, based on the concat() method of the String class:

使用findVirtual()方法允许我们为一个对象方法创建一个MethodHandle。让我们创建一个,基于String类的concat()方法。

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

5.2. Method Handle for Static Methods

5.2.静态方法的方法柄

When we want to gain access to a static method, we can instead use the findStatic() method:

当我们想获得一个静态方法的访问权时,我们可以改用findStatic()方法。

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

MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt);

In this case, we created a method handle that converts an array of Objects to a List of them.

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

5.3. Method Handle for Constructors

5.3.构造函数的方法手柄

Gaining access to a constructor can be done using the findConstructor() method.

获得对构造函数的访问可以通过findConstructor()方法完成。

Let’s create a method handles that behaves as the constructor of the Integer class, accepting a String attribute:

让我们创建一个方法句柄,表现为Integer类的构造函数,接受一个String属性。

MethodType mt = MethodType.methodType(void.class, String.class);

MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt);

5.4. Method Handle for Fields

5.4.字段的方法手柄

Using a method handle it’s possible to gain access also to fields.

使用一个方法句柄可以获得对字段的访问。

Let’s start defining the Book class:

让我们开始定义类。

public class Book {
    
    String id;
    String title;

    // constructor

}

Having as precondition a direct access visibility between the method handle and the declared property, we can create a method handle that behaves as a getter:

作为前提条件,在方法句柄和声明的属性之间有一个直接的访问可见性,我们可以创建一个方法句柄,它表现为一个getter。

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

For further information on handling variables/fields, give a look at the Java 9 Variable Handles Demystified, where we discuss the java.lang.invoke.VarHandle API, added in Java 9.

有关处理变量/字段的进一步信息,请看Java 9 Variable Handles Demystified,其中我们讨论了Java 9中添加的java.lang.invoke.VarHandle> API。

5.5. Method Handle for Private Methods

5.5.私有方法的方法柄

Creating a method handle for a private method can be done, with the help of the java.lang.reflect API.

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

Let’s start adding a private method to the Book class:

让我们开始向Book类添加一个private方法。

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

Now we can create a method handle that behaves exactly as the formatBook() method:

现在我们可以创建一个方法句柄,其行为与formatBook()方法完全一样。

Method formatBookMethod = Book.class.getDeclaredMethod("formatBook");
formatBookMethod.setAccessible(true);

MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);

6. Invoking a Method Handle

6.调用一个方法手柄

Once we’ve created our method handles, use them is the next step. In particular, the MethodHandle class provides 3 different way to execute a method handle: invoke(), invokeWithArugments() and invokeExact().

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

Let’s start with the invoke option.

让我们从invoke选项开始。

6.1. Invoking a Method Handle

6.1.调用一个方法句柄

When using the invoke() method, we enforce the number of the arguments (arity) to be fixed but we allow the performing of casting and boxing/unboxing of the arguments and return types.

当使用invoke()方法时,我们强制要求参数的数量(arity)是固定的,但是我们允许对参数和返回类型进行铸造和装箱/解箱。

Let’s see how it’s possible to use the invoke() with a boxed argument:

让我们看看如何使用带框的参数的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);

In this case, the replaceMH requires char arguments, but the invoke() performs an unboxing on the Character argument before its execution.

在这种情况下,replaceMH需要char参数,但是invoke()在执行之前对Character参数进行了解盒处理。

6.2. Invoking With Arguments

6.2.带参数的调用

Invoking a method handle using the invokeWithArguments method, is the least restrictive of the three options.

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

In fact, it allows a variable arity invocation, in addition to the casting and boxing/unboxing of the arguments and of the return types.

事实上,除了对参数和返回类型进行铸造和装箱/拆箱之外,它还允许一个可变节数的调用。

Coming to practice, this allows us to create a List of Integer starting from an array of int values:

在实践中,这允许我们从一个int值的array开始创建一个IntegerList

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. Invoking Exact

6.3.调用Exact

In case we want to be more restrictive in the way we execute a method handle (number of arguments and their type), we have to use the invokeExact() method.

如果我们想在执行方法句柄(参数的数量和它们的类型)方面有更多的限制,我们必须使用invokeExact()方法。

In fact, it doesn’t provide any casting to the class provided and requires a fixed number of arguments.

事实上,它不提供任何对所提供的类的铸造,并且需要一个固定数量的参数。

Let’s see how we can sum two int values using a method handle:

让我们看看我们如何使用方法句柄来sum两个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);

If in this case, we decide to pass to the invokeExact method a number that isn’t an int, the invocation will lead to WrongMethodTypeException.

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

7. Working With Array

7.使用阵列工作

MethodHandles aren’t intended to work only with fields or objects, but also with arrays. As a matter of fact, with the asSpreader() API, it’s possible to make an array-spreading method handle.

方法句柄并不是只针对字段或对象,也可以针对数组。事实上,通过asSpreader() API,我们可以制作一个数组传播的方法柄。

In this case, the method handle accepts an array argument, spreading its elements as positional arguments, and optionally the length of the array.

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

Let’s see how we can spread a method handle to check if the elements within an array are equals:

让我们看看如何传播一个方法句柄来检查一个数组内的元素是否相等。

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. Enhancing a Method Handle

8.加强方法的处理

Once we’ve defined a method handle, it’s possible to enhance it by binding the method handle to an argument without actually invoking it.

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

For example, in Java 9, this kind of behaviour is used to optimize String concatenation.

例如,在Java 9中,这种行为被用来优化String连接。

Let’s see how we can perform a concatenation, binding a suffix to our 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 Enhancements

9.Java 9的增强功能

With Java 9, few enhancements were made to the MethodHandles API with the aim to make it much easier to use.

在Java 9中,对MethodHandles API进行了一些改进,目的是使其更容易使用。

The enhancements affected 3 main topics:

这些改进影响了3个主要议题。

  • Lookup functions – allowing class lookups from different contexts and support non-abstract methods in interfaces
  • Argument handling – improving the argument folding, argument collecting and argument spreading functionalities
  • Additional combinations – adding loops (loop, whileLoop, doWhileLoop…) and a better exception handling support with the tryFinally

These changes resulted in few additional benefits:

这些变化带来的额外好处很少。

  • Increased JVM compiler optimizations
  • Instantiation reduction
  • Enabled precision in the usage of the MethodHandles API

Details of the enhancements made are available at the MethodHandles API Javadoc.

关于所做的改进的细节可在MethodHandles API Javadoc中找到。

10. Conclusion

10.结论

In this article, we covered the MethodHandles API, what they’re and how we can use them.

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

We also discussed how it relates to the Reflection API and since the method handles allow low-level operations, it should be better to avoid using them, unless they fit perfectly the scope of the job.

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

As always, the complete source code for this article is available over on Github.

与往常一样,本文的完整源代码可在Github上获得