510. Java 方法句柄 - 调用方式

60 阅读2分钟

510. Java 方法句柄 - 调用方式

在获得一个 MethodHandle 之后,我们有多种方式来调用它。无论使用哪种方式,底层最终都会走到 MethodHandle.invokeExact()。


1. invokeExact() —— 最严格的方式 🎯

  • 特点:参数和返回值类型必须与 MethodType 精确匹配。
  • 优势:性能最佳(几乎接近直接方法调用)。
  • 限制:不能有任何类型不一致,否则直接抛异常。

示例:调用 String.replace(char oldChar, char newChar):

MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType replaceType = MethodType.methodType(String.class, char.class, char.class);
MethodHandle replaceHandle = lookup.findVirtual(String.class, "replace", replaceType);

String result = (String) replaceHandle.invokeExact("dummy", 'd', 'm');
System.out.println(result); // "mummy"

⚠️ 注意:如果参数或返回值有细微差异,比如传 Object 而不是 char,就会报错。


2. invoke() —— 更宽松的方式 🤹

  • 特点:会尝试自动适配参数类型。
  • 原理:JVM 会在调用前生成一个“调整过的” MethodHandle,然后再用 invokeExact 调用它。
  • 代价:比 invokeExact() 稍慢,但灵活性更高。

示例:

String result = (String) replaceHandle.invoke((Object) "dummy", (Object) 'd', (Object) 'm');
System.out.println(result); // "mummy"

👉 上面这段代码如果用 invokeExact() 会报错,因为参数是 Object 类型而不是 char。


3. invokeWithArguments() —— 使用数组传参 🧳

  • 特点:接受 Object... 或 List<Object> 作为参数。
  • 用途:适合参数数量不确定、动态组装参数的场景。
  • 特殊行为:如果传入的参数数量 超过方法需要的数量,多余的参数会自动“打包”成最后一个参数的数组。

示例:

MethodHandle concatHandle = lookup.findVirtual(String.class, "concat",
        MethodType.methodType(String.class, String.class));

String result = (String) concatHandle.invokeWithArguments("Hello ", "World!");
System.out.println(result); // Hello World!

🎁 特殊情况演示: 假设方法需要一个 String[] 参数,如果你传入多个多余的实参,它们会被“挤压”成这个数组。


4. 对比总结 📊

调用方式类型检查灵活性性能典型场景
invokeExact()严格匹配最低最高高性能调用,签名已知且固定
invoke()自动适配较高次高参数类型可能变化
invokeWithArguments()最宽松最高最低动态参数组装、数量不确定

类比 🚀

  • invokeExact() 👉 好比点名“张三”,必须到本人,否则不算。
  • invoke() 👉 好比点名“张三”时,李四冒充也行(JVM 自动帮你做“伪装”)。
  • invokeWithArguments() 👉 好比直接说“谁来都行,把一堆人按顺序塞进去”。