MethodHandle详解

1.JDK何时引入

Methedhandle Java7 引入的重要的API,该包提供了一种新的动态调用方法的机制。

2.什么是MethodHandle?

MethodHandle 是对基础方法,构造函数,字段或类似的低级操作的类型化,直接可执行的引用,并带有自变量或返回值的可选转换。 

 3.创建和使用MethodHandle,四个步骤

(1)创建查找
(2)创建方法类型
(3)查找方法句柄
(4)调用方法句柄

4.MethodHandle 和反射

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

但是如果考虑到性能不是一项任务的唯一适用性衡量指标,我们还必须考虑到,由于缺少诸如成员类枚举,可访问性标志检查等机制,MethodHandlesAPI 难以使用。
尽管如此,但是Methodhandles API 提供了改变参数类型和参数顺序。

5.步骤1-创建查找

当我们要创建方法句柄时,要做的第一件事是检索查找,工厂对象负责为查找类可见的方法,构造函数和字段创建方法句柄。
通过MethodHandles API,可以创建具有不同访问模式的查找对象。

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

MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();复制代码
但是,如果我们还希望访问私有方法和受保护的方法,则可以使用lookup()方法:

MethodHandles.Lookup lookup = MethodHandles.lookup();复制代码

6.步骤2-创建一个MethodType

为了能够创建MethodHandle,查找对象需要定义其类型,这是通过MethodType类实现的。
特别是,一个MethodType代表的参数和返回类型接受和方法返回的句柄,或者传递,并通过方法调用者手柄预期。
MethodType的结构很简单,它由返回类型和适当数量的参数类型组成,必须在方法句柄及其所有调用者之间正确匹配。
以同样的方式作为MethodHandle,一个连的情况下MethodType是不可变的。
让我们看看如何定义一个MethodType,该方法将java.util.List类指定为返回类型,并将Object数组指定为输入类型:

MethodType mt = MethodType.methodType(List.class, Object[].class);复制代码
如果该方法返回原始类型或void作为其返回类型,我们将使用表示这些类型的类(void.class,int.class…)。
让我们定义一个MethodType,它返回一个int值并接受一个Object:

MethodType mt = MethodType.methodType(int.class, Object.class);复制代码
现在,我们可以继续创建MethodHandle。

7.步骤3-创建一个MethodHandle

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

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

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

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

(3)构造函数
可以使用findConstructor()方法来访问构造函数。
让我们创建一个充当Integer类的构造函数的方法句柄,接受一个String属性:

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

(4)字段
使用方法句柄,还可以访问字段。

public class Book 
{         
String id;    String title;     
// constructor 
}
MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class);
复制代码


(5)私有方法
可以在java.lang.reflect API 的帮助下为私有方法创建方法句柄。
让我们开始向Book类添加一个私有方法:

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

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


7.步骤4-调用方法句柄

创建方法句柄后,下一步就是使用它们。特别是,MethodHandle类提供了三种执行方法句柄的方法:invoke(),invokeWithArugments()和invokeExact()。

(1)invoke方法
当使用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);复制代码

(2)invokeWithArguments方法
使用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));复制代码


(3)invokeExact方法
如果我们希望在执行方法句柄(参数数量及其类型)方面受到更多限制,则必须使用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);复制代码


8.重要举例

MethodHandle.invoke()并且MethodHandle.invokeExact()是特殊方法,其行为与其他可变arity方法不同:

与虚拟方法一样,源级调用invokeExact和invoke编译一条invokevirtual指令。更不寻常的是,编译器必须记录实际的参数类型,并且不能对arguments执行方法调用转换。
相反,它必须根据它们自己未转换的类型将它们压入堆栈。方法句柄对象本身在参数之前被压入堆栈。然后,编译器使用符号类型描述符来调用方法句柄,该符号类型描述符描述了参数和返回类型。
因此,当您调用这些方法时,参数的类型确实很重要。如果要将参数作为传递Object[],则应invokeWithArguments()改用:mh.invokeWithArguments(myArray);








9.总结

总结这三个方法的差异:
invoke,invokeExact 传入参数时,必须与方法签名参数是一致的,不能以参数合成数组进行传递
重要的是invoke 方法可以提供参数自动装箱与拆箱,但是invokeExact 则必须是精确的参数类型,不能进行自动的装箱拆箱。

invokeWithArguments 方法调用则可以Object[] 数组的形式进行调用


猜你喜欢

转载自juejin.im/post/5eef9f9a51882565d02918f7