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()👉 好比直接说“谁来都行,把一堆人按顺序塞进去”。