最安全获取Class对象的方式是使用已加载类的.class字面量(如String.class),编译期校验,避免ClassNotFoundException;Class.forName需谨慎处理类名字符串错误。

Class对象怎么获取才安全可靠
直接用 Class.forName("com.example.MyClass") 最常见,但要注意类名字符串写错会抛 ClassNotFoundException;更稳妥的是用已加载类的 .class 字面量,比如 String.class,编译期就校验,不会出错。
泛型擦除后,List 是非法语法,得用 List.class;如果真需要带泛型信息,得靠 ParameterizedType 从方法返回类型或字段类型里反推。
- 避免在热更/模块卸载场景下缓存
Class对象,可能指向已失效的类加载器 -
getClass()返回运行时实际类型,对子类实例调用会得到子类的Class,不是声明类型的 - 通过类加载器获取类时,注意双亲委派——自定义类加载器加载的类和系统加载的同名类不等价
Method.invoke() 执行失败的几个典型原因
最常踩的坑是没设 setAccessible(true) 就调私有方法,直接抛 IllegalAccessException;还有参数类型不匹配,比如传了 int 却期望 Integer,自动装箱不生效,得手动包装或用 getGenericParameterTypes() 校验。
另一个隐蔽问题是访问权限检查开销:频繁调用 invoke() 且方法本身很轻量时,反射开销可能比方法逻辑本身还高。
立即学习“Java免费学习笔记(深入)”;
- 静态方法调用时,第一个参数传
null,别传实例对象 - 可变参数方法(
void f(String... args))传参要包成数组,如invoke(obj, new Object[]{new String[]{"a", "b"}}) - 如果目标方法抛出受检异常,
invoke()会把它包进InvocationTargetException,需用e.getCause()取原始异常
Field.get() 和 set() 为什么读不到值或报错
字段值读取为 null 或默认值,大概率是没正确指定对象实例——对静态字段,get(null) 合法;对实例字段,必须传对应类的实例,传错类型或 null 都会抛 IllegalArgumentException。
字段被 final 修饰时,set() 在大多数 JDK 版本里仍能成功(尤其配合 setAccessible(true)),但这是未定义行为,JVM 可能优化掉后续读取,导致值“看似没改”。Java 17+ 对强封装类(如 java.lang.String)限制更严,setAccessible(true) 也可能失败。
- 基本类型字段(如
int)用getInt()/setInt()等专用方法,比通用get()/set()更快且不触发装箱 - 字段名拼写错误不会编译报错,运行时报
NoSuchFieldException,建议用 IDE 的 refactoring 工具同步改名 - 字段值被 JIT 优化缓存后,反射修改可能不立即对其他线程可见,需配合
Unsafe或 volatile 语义
反射操作影响 JVM 内联和 JIT 编译吗
会影响。JVM 通常不会对包含反射调用的方法做深度内联,尤其是 Method.invoke() 这种动态分派入口。多次调用同一 Method 对象时,JIT 可能生成适配代码,但远不如直接调用高效。
真正影响性能的不是反射本身,而是它阻断了编译器的类型流分析和逃逸分析。比如一个反射创建的对象,JIT 很难判定其是否逃逸,从而禁用栈上分配。
- 高频场景(如 ORM 字段赋值、RPC 参数解析)建议缓存
Method和Field对象,避免重复getDeclaredMethod() - Java 9+ 可考虑
MethodHandle替代Method.invoke(),启动慢但长期运行更快,且支持更细粒度的权限控制 - 模块化(JPMS)下,默认禁止跨模块反射访问,需在
module-info.java中显式声明opens或exports










