Method.invoke()动态调用方法的关键是参数严格匹配、非public方法必须setAccessible(true)、异常需解包getCause()处理;否则会抛IllegalArgumentException、IllegalAccessException或InvocationTargetException。

用 Method.invoke() 动态调用方法,关键不在“能不能调”,而在“参数对不对、权限够不够、异常抓得准”。稍有疏忽,IllegalArgumentException、IllegalAccessException 或 InvocationTargetException 就会立刻报错,而真正的问题往往藏在表象之下。
参数类型必须严格匹配,不自动转型
Java 反射不会帮你做隐式类型转换,也不会靠继承关系“凑合”传参。比如目标方法是 void setValue(long v),你传 Integer.valueOf(100) 就会失败,哪怕 int 能自动转 long —— 反射不走这个流程。
- 调用前先检查:用
method.getParameterTypes()拿到每个形参的Class,再比对实参的.getClass() - 基本类型优先用包装类静态工厂:如
Integer.valueOf(42)、Long.valueOf(100L),避免new Integer(42)(已废弃) - 从 JSON 或泛型字段读出的值常是
Object类型,需显式转换:例如字段值是42L,方法要int,就得写((Number) obj).intValue() - 数组参数要包一层:
void process(String[] arr)的调用不能直接传new String[]{"a"},得写成new Object[]{new String[]{"a"}}
私有/受保护方法必须设 setAccessible(true)
默认情况下,Method.invoke() 只允许访问 public 方法。调用 private、protected 或包级私有方法时,不加 setAccessible(true) 必抛 IllegalAccessException。
- 不是“建议加”,而是“不加就失败”——这是 JVM 访问控制的硬性限制
- JDK 9+ 模块系统下,即使方法是 public,跨模块调用非 open 模块的类也可能受限;统一加
setAccessible(true)是最稳妥的做法 -
setAccessible(true)只影响当前Method实例,不影响其他反射对象,也不需要手动恢复为false - 注意:生产环境若启用了
SecurityManager,该操作可能被拒绝
返回值和异常处理不能只 catch Exception
invoke() 的返回值总是 Object:方法返回 void 时结果为 null;返回基本类型时会自动装箱。但更易踩坑的是异常——所有方法内抛出的异常(包括 RuntimeException 和受检异常),都会被包装进 InvocationTargetException。
- 别写
catch (Exception e),它会掩盖真实错误源,也分不清是参数错还是业务逻辑崩了 - 应明确捕获三类异常:
IllegalAccessException(权限不足)、IllegalArgumentException(参数数量或类型错)、InvocationTargetException(方法执行中出错) - 对
InvocationTargetException,务必调用e.getCause()获取原始异常;若原方法声明 throwsIOException,那getCause()就是它 - 若需向上抛受检异常,可直接
throw (IOException) e.getCause()(需方法签名声明)或包装为运行时异常
Android 和新 JDK 下的特殊注意事项
在 Android(尤其旧版 ART)中,反射调用性能明显低于直接调用;JDK 12+ 默认启用强封装后,跨模块反射行为更保守。
- Android 上建议:高频调用场景尽量避免反射;ProGuard/R8 可能移除未显式保留的私有方法,记得在
proguard-rules.pro中 keep 相关类和方法 - JDK 17+ 使用
--add-opens启动参数可临时放宽模块限制,例如:--add-opens java.base/java.lang=ALL-UNNAMED - 单元测试中可用
try-with-resources配合ReflectiveOperationException统一兜底,但生产代码仍推荐按异常类型精细处理

















