java方法句柄-----4.你所不知道的MethodHandle【翻译】

时间:2023-03-09 19:13:58
java方法句柄-----4.你所不知道的MethodHandle【翻译】

Method Handles in Java

1.介绍

在本文中,我们将探讨一个重要的API,它是在Java 7中引入的,并在Java 7版本之后更加完善:全限定名是:Java.lang.invoke.MethodHandles

我们将学习什么是method handles,如何创建它们和如何使用他们。

2.什么是MethodHandle

MethodHandle来自Oracle java7的定义:

"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. These transformations are quite general, and include such patterns as conversion, insertion, deletion, and substitution. "

这个定义暗示着:MethodHandle:

  • 方法的引用。和c++/c中不大相同的是,是对引用的方法的限制:即引用的方法和MethodHandle的type(参数和返回值)必须一样。
  • Executable。An Methodhandle can be executed by invoking its methods: invokeExact..
  • Method Type transformations.

    简单地说,方法句柄是low-level mechanism(为了查找、调整和调用方法)。

    a low-level mechanism for finding, adapting and invoking methods.

创建和使用一个MethodHandle,需要进行一下四个步骤:

  • Creating the lookup // 创建lookup
  • Creating the method type // 创建method type
  • Finding the method handle // 找到method handle
  • Invoking the method handle // 调用

3. Method Handles vs Reflection

Method Handles的引入是为了与已经存在的java.lang.reflect API相配合。他们分别是为了解决不同的问题而出现的。从性能角度上说,MethodHandle api要比反射快很多因为访问检查在创建的时候就已经完成了,而不是像反射一样等到运行时候才检查。但同时,Method Handles比反射更难用,因为没有列举类中成员,获取属性访问标志之类的机制。

另外,MethodHandles可以操作方法,更改方法参数的类型和他们的顺序。而反射则没有这些功能。

从以上角度看,反射更通用,但是安全性更差,因为可以在不授权的情况下使用反射对象。而method Handles遵从了分享者的能力。所以method handle是一种更低级的发现,适配和调用方法的方式,唯一的优点就是更快。所以反射更适合主流Java开发者,而method handle更适用于对编译和运行性能有要求的人。

4.创建method handle

要使用method handle,首先需要得到Lookup。这是创造方法,构造函数,属性的method handles的工厂类。

// public方法的Lookup
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
// 所有方法的Lookup
MethodHandles.Lookup lookup = MethodHandles.lookup();

5.创建MethodType

要创建MethodHandle,lookup需要一个定义了它的类型的MethodType对象。这里的类型包括了传入参数的类型,和最后返回的类型,要一一对应。第一个是返回类型,如果没有返回值就是Void.class, 后面是可变的传入参数的类型。

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

such as

// 接收数组,返回一个List对象,其中Object[].class作为input type 即入参类型
MethodType mt = MethodType.methodType(List.class, Object[].class);

6.查找MethodHandle

Lookup之所以叫Lookup自然是因为他们有查找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.

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.

java方法句柄-----4.你所不知道的MethodHandle【翻译】

6.1 Method Handle for Methods

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:

// 上面说过第一个入参为返回类型,后面是可变的传入参数的类型。
MethodType mt = MethodType.methodType(String.class, String.class); // 使用lookup的findVirtual函数可以获得一个对象方法的MethodHandle 。查找 the concat() method of the String class
MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);

6.2.Method Handle for Static Methods

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

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.

6.3. Method Handle for Constructors

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

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

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

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

6.4.Method Handle for Fields

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 }

Havinga as precondition direct access visibility between the method handle and the declared property , we can create a method handle that behaves as a 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.

6.5. Method Handle for Private Methods

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

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

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

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

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

7. Invoking a Method Handle -- 方法句柄的调用

在获取到了一个方法句柄之后,最直接的使用方法就是调用它所引用的底层方法。在这点上,方法句柄的使用类似于反射API中的Method类。但是方法句柄在调用时所提供的灵活性是Method类中的invoke方法所不能比的。

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().

7.1. Invoking a Method Handle

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(但是我们允许对参数和返回类型进行强制转换和装箱/拆箱。).

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

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, the invoke() performs an unboxing on the Character argument before its execution.

7.2. Invoking With Arguments

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

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:


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

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

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:

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.

方法调用细则:

有三种方法可以调用方法invoke(), invokeWithArugments()和invokeExact(),当我们使用invoke时,我们必须固定arguments的数目。

具体的区别是:

与invokeExact方法不同,invoke方法允许更加松散的调用方式。它会尝试在调用的时候进行返回值和参数类型的转换工作

这是通过MethodHandle类的asType方法来完成的,asType方法的作用是把当前方法句柄适配到新的MethodType上面,并产生一个新的方法句柄。当方法句柄在调用时的类型与其声明的类型完全一致的时候,调用invoke方法等于调用invokeExact方法;否则,invoke方法会先调用asType方法来尝试适配到调用时的类型。如果适配成功,则可以继续调用。否则会抛出相关的异常。这种灵活的适配机制,使invoke方法成为在绝大多数情况下都应该使用的方法句柄调用方式。

参考:

https://www.baeldung.com/java-method-handles

https://www.cnblogs.com/night-wind/p/4405564.html

https://blog.****.net/lyj1597374034/article/details/105250459

参考资料:《java程序员修炼之道》、《深入理解java7核心技术与最佳实践》