方法句柄

  |  

摘要: 本文主要是关于方法句柄的原理和应用。

【对算法,数学,计算机感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:算法题刷刷
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


问题背景

方法句柄(method handle)是 Java7 为了支持动态类型语言而引入的,它是对底层方法、构造方法和字段的一个类型化的可执行引用。

通过方法句柄可以直接调用该句柄所引用的底层方法,类似于反射 API 中的 Method 类。方法句柄的功能更强大、使用更灵活、性能也更好。方法句柄和反射 API 也可以协同工作的。


方法句柄的用法

(1) java.lang.invoke.MethodHandle

invoke 子包是在 Java7 中引入的,提供了与 Java 虚拟机交互的低级原语(low-level primitives)。这个包过于复杂,本文只涉及 MethodHandle 的用法。

方法句柄是由 MethodHandle 类来表示的。对于一个方法句柄来说,它的类型完全由它的参数类型和返回值类型来确定,而与它所引用的底层方法的名称和所在的类没有关系。

(2) java.lang.invoke.MethodType

方法类型由 java.lang.invoke.MethodType 类来表示,要构造一个 MethodType 类的实例,可以调用该类的静态工厂方法 methodType

methodType 方法参数列表中的第一个参数总是返回值类型,其后是 0 到多个参数的类型。类型都是由 Class 类的对象来指定的,如果返回值类型是 void,则可以用 void.class 或 java.lang.Void.class 来指定。

例子

Person 类的 run 方法签名如下:

1
public void run(int meters)

构造该方法的 MethodType 对象如下:

1
MethodType mt = MethodType.methodType(void.class, int.class);

mt 这个 MethodType 对象适用于所有返回值类型为 void、带有一个 int 参数的方法。

(3) Lookup 对象

要构造一个方法句柄,首先要得到一个 Lookup 对象,该对象是创建方法句柄的工厂

由 Lookup 创建的每个方法句柄都等同于方法的字节码行为(bytecode behavior),也就是说,JVM 调用方法句柄与执行和方法句柄相关的字节码行为一致

MethodHandles

Lookup 是在 MethodHandles 类中定义的一个静态内部类,可以调用 MethodHandles 类的静态方法 lookup 得到一个 Lookup 对象

1
MethodHandles.Loopup lookup = MethodHandles.lookup();

调用 lookup 方法返回的 Lookup 对象具有模拟调用者所有支持的字节码行为的全部功能。

(4) 利用 Loopup 对象创建方法句柄

1
MethodHandle mh = lookup.findVirtual(Person.class, "run", mt);
  • 第一个参数是要访问的方法所在的类或接口。
  • 第二个参数是方法的名字。
  • 第三个参数是方法的类型。

如果我们只是想调用特定对象的方法,那么也可以调用 Lookup 的 bind 方法,创建一个针对特定对象的方法句柄:

1
2
Person p = new Person("张三");
MethodHandle mh = lookup.bind(p, "run", mt);

(5) 执行方法句柄引用的底层方法

MethodHandle 类中的 invokeExact 或者 invoke 方法。前者在调用时要求严格的类型匹配,方法参数与返回值类型必须一致;后者允许更为松散的调用方式,它会尝试在调用的时候进行返回值和参数类型的转换工作。

(6) 执行类或接口中的私有方法

要执行类或接口中的私有方法,需要先调用 MethodHandles 类的 privateLookupIn 静态方法获取具有私有访问权限的 Lookup 对象

1
public static MethodHandles.Lookup privateLookupIn(Class<?> targetClass, MethodHandles.Lookup lookup) throws IllegalAccessException

其中两个参数含义如下:

  • targetClass: 目标类的 Class 对象
  • lookup: 调用方的 Lookup 对象

总结

大致流程如下:

1
2
3
4
(1) MethodHandles.Lookup loopup  
(2) MethodType mt
(3) MethodHandle mh
(4) mh.invoke

例子: 用方法句柄调用 Person 类的 run 方法和私有方法 helper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.lang.invoke.MethodType;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;

public class UseMethodHandle {
public static void main(String[] args) {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType mtCons = MethodType.methodType(void.class, String.class);
try {
// 得到 Person 类的构造方法的方法句柄
MethodHandle mhCons = lookup.findConstructor(Person.class, mtCons);
// 调用构造方法,得到 Person 类的对象
Person p = (Person) mhCons.invokeExact("张三");

// 开始准备调用 Person 对象的 run 方法
MethodType mt = MethodType.methodType(void.class, int.class);
MethodHandle mh = lookup.findVirtual(Person.class, "run", mt);
mh.invokeExact(p, 100);

// 开始准备调用 Person 对象的私有方法 helper
mt = MethodType.methodType(void.class);
lookup = MethodHandles.privateLookupIn(Person.class, lookup);
mh = lookup.findVirtual(Person.class, "helper", mt);
mh.invokeExact(p);
} catch (Throwable e) {
e.printStackTrace();
}
}
}

结果如下:

1
2
张三 跑了 100 米
私有的辅助方法

总结

方法句柄主要是为了支持动态类型语言而引入的,与反射 API 相比,方法句柄更偏底层一些,某些功能还需要对字节码的格式有所了解。


Share