1. 引言

本文将探讨 Java 7 引入并在后续版本中增强的重要 API——java.lang.invoke.MethodHandles。我们将深入理解方法句柄的概念、创建方式及使用技巧。

2. 什么是方法句柄?

根据官方文档的定义:

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

简单来说,方法句柄是查找、适配和调用方法的低级机制。方法句柄具有不可变性且无可见状态。

创建和使用 MethodHandle 需要四个步骤:

  1. 创建查找对象(Lookup)
  2. 创建方法类型(MethodType)
  3. 查找方法句柄
  4. 调用方法句柄

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 进行了三方面增强:

  1. 查找功能增强

    • 支持不同上下文的类查找
    • 支持接口中的非抽象方法
  2. 参数处理改进

    • 优化参数折叠(argument folding)
    • 改进参数收集(collecting)和展开(spreading)
  3. 新增组合功能

    • 添加循环控制(loop, whileLoop, doWhileLoop
    • 增强异常处理(tryFinally

这些改进带来:

  • 提升 JVM 编译器优化能力
  • 减少实例化开销
  • 提高方法句柄使用精度

10. 总结

本文全面介绍了 MethodHandles API 的概念与使用。虽然方法句柄支持低级操作,但除非完全符合需求,否则应谨慎使用。相比反射,它在特定场景下提供更好的性能和灵活性。

完整示例代码见 GitHub 仓库


原始标题:Method Handles in Java