开始前,会这些就够了
- 理解
int与Integer的区别、装箱拆箱和静态方法。 - 先读过反射的基本定位流程;不要求掌握字节码。
- 先读 反射、注解与动态代理如何连起来:先理解运行时定位方法与执行方法的区别,再读调用点类型匹配。
你把一个接收 int、返回 int 的静态方法变成 MethodHandle。调用参数仍然是数值 21,只是接收结果的变量改成 Object,运行时就出现 WrongMethodTypeException。它为什么不像普通方法调用那样自动把 int 装箱?
先不要把这个入口当作一块神秘的反射魔法。本实验只定位本类自己的 twice 方法,打印它的输入和输出类型,再改变调用处的类型。每次目标方法执行都会增加计数,用来辨别“没有进入方法”和“方法自己抛错”。
把例子带到自己的电脑上
下载完整练习包
需要 JDK 17 或更新版本,使用 Java 标准库;制作时实测 Windows / JDK 17.0.20.1 / 21.0.12。使用标准库,不需要安装第三方包。下载 ZIP 后选择“全部提取”,进入能看到 README.md 的文件夹。
下载 ZIP · 5 个文件包里有什么
- ExactHandleDemo.java:完整可运行示例
- README.md:运行说明与独立练习
- observed.stdout.txt:本次实际输出
- verification.json:源码和产物验证记录
- SHA256SUMS.txt:内容摘要
在这个文件夹打开 PowerShell,逐行运行
java -version
javac -version
javac -encoding UTF-8 --release 17 ExactHandleDemo.java
java -cp . ExactHandleDemo解压到独立练习文件夹,在含 ExactHandleDemo.java 的目录打开终端运行。Windows 本类静态twice;int/Integer/Object调用点、返回与参数不匹配、显式拆箱/invoke/asType及目标异常;JDK17/21各12项断言。未测任意转换、null、模块访问、插件或性能。
找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。
第一步:方法句柄也有完整的输入输出形状
MethodType.methodType(int.class, int.class) 的第一个参数是返回类型,后面才是参数类型。程序定位静态方法后打印 (int)int,含义是接收一个 int、返回一个 int。
findStatic 使用类、方法名和这个类型找到可访问的目标。这里没有 receiver 参数,因为目标是静态方法;如果以后换成实例方法,不能照抄本例类型列表而不检查接收者。
第一次调用写为 int exact = (int) mh.invokeExact(21),参数类型和返回预期都为 int,得到 42;目标计数变成一。此时我们建立了一个确定能工作的基线。
第二步:接收方式也是调用点类型的一部分
下一次调用写成 Object value = mh.invokeExact(21)。虽然目标理论上能返回一个可装箱的数字,但调用点此时期待 Object,而句柄实际返回 int;invokeExact 要求两边精确匹配,于是抛类型异常。
再把参数放入 Integer boxed,并仍用 (int) 接返回值,错误变成参数侧:调用点传的是 Integer,目标需要 int。数值都为 21,不能让类型差异消失。
两次异常后 calls 仍然为一,说明目标没有执行。这个观测比只打印异常类更有用:它把调用连接失败和目标业务代码失败分开了。
第三步:选择明确转换,或明确使用适配入口
程序展示三条成功路径。第一条手动调用 intValue,让 invokeExact 的参数恢复为 int;第二条使用 invoke,允许本例所需的适配;第三条用 asType 创建一个接收 Integer、返回 Object 的新句柄,再对它执行 invokeExact。
新句柄打印为 (Integer)Object,返回结果是装箱后的 42。这里精确匹配仍然存在,只是匹配的目标已从原句柄换成适配后的类型。
适配并不表示任意对象都能变成整数。类型转换、拆箱或目标执行仍可能失败;本实验没有把 String、null 或所有基本类型的转换规则全部列完。不要用宽泛的捕获掩盖是哪一步失败。
第四步:类型完全匹配,目标仍可能拒绝输入
最后传入 int 值 -1,参数和返回预期都匹配,所以真正进入 twice;calls 变成五,然后目标抛出本例自己的 IllegalArgumentException。
这一组和前面两个 WrongMethodTypeException 的差别是执行位置。排错记录应同时保留句柄类型、调用处的参数与返回预期、目标是否进入,以及实际异常。
实跑限于本类静态方法、int/Integer/Object、三条适配方式和一个目标异常。没有做速度比较、越权访问、模块边界或任意插件加载;这个实验也不能证明它比反射在你的项目里更快。
运行单文件,按实际输出逐项核对
保存为 ExactHandleDemo.java,在源码目录执行下方命令;需要完整 JDK 17 或更新版本。无需 Maven、网络或第三方依赖。
2026 年 10 月 18 日在 Windows 上分别以 Microsoft OpenJDK 17.0.20.1 与 21.0.12、--release 17 编译运行,输出一致。每项断言失败都会终止程序;下方是本次真实输出。
展开收起代码 · 小实验
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
import java.lang.invoke.*;
public class ExactHandleDemo {
static int checks,calls;
static void check(boolean ok) { if(!ok) throw new AssertionError(); checks++; }
static int twice(int value) {
calls++;
if(value<0) throw new IllegalArgumentException("negative input");
return value*2;
}
public static void main(String[] args) throws Throwable {
MethodType type=MethodType.methodType(int.class,int.class);
MethodHandle mh=MethodHandles.lookup().findStatic(ExactHandleDemo.class,"twice",type);
check(mh.type().equals(type));
int exact=(int)mh.invokeExact(21);
check(exact==42); check(calls==1);
System.out.println("handle_type="+mh.type()+"; exact_int="+exact);
boolean wrongReturn=false;
try { Object value=mh.invokeExact(21); } catch(WrongMethodTypeException e) { wrongReturn=true; }
Integer boxed=Integer.valueOf(21);
boolean wrongParameter=false;
try { int value=(int)mh.invokeExact(boxed); } catch(WrongMethodTypeException e) { wrongParameter=true; }
check(wrongReturn); check(wrongParameter); check(calls==1);
System.out.println("Object_return=WrongMethodTypeException; Integer_parameter=WrongMethodTypeException; target_calls="+calls);
int explicit=(int)mh.invokeExact(boxed.intValue());
int flexible=(int)mh.invoke(boxed);
MethodHandle adapted=mh.asType(MethodType.methodType(Object.class,Integer.class));
Object adaptedResult=adapted.invokeExact(boxed);
check(explicit==42); check(flexible==42); check(adaptedResult.equals(Integer.valueOf(42))); check(calls==4);
System.out.println("explicit_unbox="+explicit+"; invoke_adaptation="+flexible+"; adapted_type="+adapted.type()+"; adapted_value="+adaptedResult);
boolean targetFailure=false;
try { int value=(int)mh.invokeExact(-1); }
catch(IllegalArgumentException e) { targetFailure=e.getMessage().equals("negative input"); }
check(targetFailure); check(calls==5);
System.out.println("target_failure=IllegalArgumentException; target_calls="+calls);
System.out.println("PASS checks="+checks);
}
}
怎样运行:在源码所在目录,先单独执行 javac -encoding UTF-8 --release 17 ExactHandleDemo.java;编译成功后,再执行 java -cp . ExactHandleDemo。两条命令分别回车。2026-10-18 已在 Windows、JDK 17.0.20.1 / 21.0.12 编译运行此份源码并通过检查。Windows 本类静态twice;int/Integer/Object调用点、返回与参数不匹配、显式拆箱/invoke/asType及目标异常;JDK17/21各12项断言。未测任意转换、null、模块访问、插件或性能。
运行后对照结果
handle_type=(int)int; exact_int=42
Object_return=WrongMethodTypeException; Integer_parameter=WrongMethodTypeException; target_calls=1
explicit_unbox=42; invoke_adaptation=42; adapted_type=(Integer)Object; adapted_value=42
target_failure=IllegalArgumentException; target_calls=5
PASS checks=12记住这三点
invokeExact比较的是参数和返回值的完整类型,数值相同还不够。- 调用点的返回预期也会参与类型匹配。
- 适配失败与目标方法抛错需要分别定位。
别把例子用过头
- 只检查参数值,忽略调用点的声明类型。
- 把返回值改接到
Object后,仍假设会自动装箱。 - 把invoke或
asType理解为任意输入都可转换。 - 从小实验推断实际框架性能。
自己试一小步
给这个方法增加一个 long 版本后,你能用观测区分查找失败、类型匹配失败和目标失败吗?
- 先列出两个目标方法的完整
MethodType,标出返回类型。 - 设计
int、long、Integer、Long四种参数声明的调用矩阵,先写预测。 - 选择有限的适配路径,每次记录句柄类型、输出或异常以及目标进入次数。
- 提交矩阵、源码、运行版本和未测试的转换。
做到这些,就算完成
- 参数和返回类型都纳入比较。
- 错误发生在查找、调用匹配还是目标执行能够辨别。
- 没有将结果包装成未经基准测试的性能结论。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- MethodHandle:精确调用与类型适配 (新标签页)↗
支持
invokeExact、invoke、asType及调用点类型。 - MethodHandles.Lookup.findStatic:定位静态目标 (新标签页)↗
支持访问检查、静态目标类型和查找异常。
- MethodType:输入与返回类型一起表示 (新标签页)↗
支持类型工厂参数顺序、相等判断和类型展示。