1. 引言
本文将探讨 Java 7 引入并在后续版本中增强的重要 API——java.lang.invoke.MethodHandles
。我们将深入理解方法句柄的概念、创建方式及使用技巧。
2. 什么是方法句柄?
根据官方文档的定义:
方法句柄是对底层方法、构造器、字段或类似低级操作的类型化、直接可执行引用,可对参数或返回值进行可选转换。
简单来说,方法句柄是查找、适配和调用方法的低级机制。方法句柄具有不可变性且无可见状态。
创建和使用 MethodHandle
需要四个步骤:
- 创建查找对象(Lookup)
- 创建方法类型(MethodType)
- 查找方法句柄
- 调用方法句柄
2.1 方法句柄 vs 反射
方法句柄作为对现有 java.lang.reflect
API 的补充,两者服务于不同目的且特性各异:
✅ 性能优势:MethodHandles
API 通常比反射 API 更快,因为访问检查在创建时而非执行时完成。当存在安全管理器时,这种差异会被放大。
❌ 易用性劣势:MethodHandles
API 缺乏成员类枚举、可访问性标志检查等机制,使用更复杂。
⚠️ 独特能力:方法句柄支持方法柯里化、参数类型转换和参数顺序调整。
3. 创建查找对象(Lookup)
创建方法句柄的第一步是获取查找对象——负责为可见的方法、构造器和字段创建方法句柄的工厂对象。
通过 MethodHandles
API 可创建不同访问模式的查找对象:
// 提供公共方法访问权限
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
// 提供私有/受保护方法访问权限
MethodHandles.Lookup lookup = MethodHandles.lookup();
4. 创建方法类型(MethodType)
要创建 MethodHandle
,需通过 MethodType
类定义其类型。**MethodType
表示方法句柄接受和返回的参数类型与返回类型**。
MethodType
结构简单,由返回类型和匹配的参数类型组成。与 MethodHandle
类似,MethodType
实例也是不可变的。
定义返回 List
、接受 Object[]
的方法类型:
MethodType mt = MethodType.methodType(List.class, Object[].class);
处理基本类型或 void
返回类型时,使用对应的类(如 void.class
, int.class
):
MethodType mt = MethodType.methodType(int.class, Object.class);
5. 查找方法句柄
定义方法类型后,可通过查找对象获取 MethodHandle
,需提供源类和方法名。查找工厂提供多种方法,根据方法范围选择合适方式:
5.1 对象方法句柄
使用 findVirtual()
创建对象方法句柄:
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);
5.3 构造器句柄
使用 findConstructor()
访问构造器:
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;
// 构造器
}
创建 getter 方法句柄:
MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class);
💡 更多字段操作可参考 Java 9 的
VarHandle
API
5.5 私有方法句柄
结合反射 API 创建私有方法句柄。添加私有方法:
private String formatBook() {
return id + " > " + title;
}
创建方法句柄:
Method formatBookMethod = Book.class.getDeclaredMethod("formatBook");
formatBookMethod.setAccessible(true);
MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);
6. 调用方法句柄
MethodHandle
提供三种调用方式:invoke()
, invokeWithArguments()
, invokeExact()
。
6.1 基础调用(invoke)
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);
6.2 可变参数调用(invokeWithArguments)
invokeWithArguments
最灵活,支持:
- 可变参数数量
- 类型转换
- 自动装箱/拆箱
示例:将 int
数组转为 List<Integer>
:
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)
invokeExact()
最严格,要求:
- 参数数量完全匹配
- 无类型转换
- 无自动装箱/拆箱
示例:整数求和:
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
类型会抛出 WrongMethodTypeException
7. 数组操作
方法句柄也支持数组操作。使用 asSpreader()
创建数组展开方法句柄:
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 用此优化字符串拼接:
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 进行了三方面增强:
查找功能增强
- 支持不同上下文的类查找
- 支持接口中的非抽象方法
参数处理改进
- 优化参数折叠(argument folding)
- 改进参数收集(collecting)和展开(spreading)
新增组合功能
- 添加循环控制(
loop
,whileLoop
,doWhileLoop
) - 增强异常处理(
tryFinally
)
- 添加循环控制(
这些改进带来:
- 提升 JVM 编译器优化能力
- 减少实例化开销
- 提高方法句柄使用精度
10. 总结
本文全面介绍了 MethodHandles
API 的概念与使用。虽然方法句柄支持低级操作,但除非完全符合需求,否则应谨慎使用。相比反射,它在特定场景下提供更好的性能和灵活性。
完整示例代码见 GitHub 仓库