java反射是jvm运行时暴露类元数据的机制,通过class及配套api访问已存在结构,不改变字节码;需注意类加载、初始化、访问控制、泛型获取与模块封装等关键细节。

反射本质是 JVM 在运行时暴露的类元数据接口
Java 反射不是“黑魔法”,而是 JVM 把原本编译期就确定的 Class 结构(字段、方法、构造器、注解等)通过 java.lang.Class 和配套 API 暴露给程序。它不改变字节码,也不绕过访问控制——只是让代码能按名字查到并调用那些本就存在的结构。
常见错误现象:ClassNotFoundException 或 NoClassDefFoundError 不是反射特有,而是类路径或类加载时机问题;IllegalAccessException 通常是因为没调用 setAccessible(true) 就去访问 private 成员。
- 必须确保目标类已被加载(比如已 new 过、被 static 引用过,或显式
Class.forName("X")) -
Class.forName()默认会触发初始化(执行 static 块),ClassLoader.loadClass()不会——这点在热加载或配置驱动场景里容易踩坑 - 反射调用方法/字段性能比直接调用低一个数量级,但 JDK 9+ 对
Method.invoke()做了内联优化,热点代码影响变小
获取 Class 对象的三种方式及其语义差异
不是所有写法都等价。选错方式会导致类没加载、初始化被跳过,或无法拿到泛型信息。
-
String.class:最轻量,不触发初始化,适用于已知类名且无需初始化的场景(如类型判断、泛型擦除后取原始类型) -
obj.getClass():安全可靠,返回运行时实际类型(可能不是声明类型),适合多态场景下探查真实类型 -
Class.forName("java.util.ArrayList"):会触发初始化,且要求类在 classpath 中;若传入空字符串或拼错类名,抛ClassNotFoundException
注意:ClassLoader.getSystemClassLoader().loadClass("X") 和 forName 的区别在于前者不初始化类,适合延迟加载或避免副作用。
调用私有方法/字段前必须调用 setAccessible(true)
这是反射中最常被忽略的一步。JVM 默认启用封装检查,即使你拿到了 Field 或 Method 对象,不设为可访问就会在 get() 或 invoke() 时抛 IllegalAccessException。
- 该调用只对当前
Field/Method实例生效,不影响其他对象 - 在安全管理器(SecurityManager)启用的环境(如老版 Applet 或某些容器)中,
setAccessible(true)可能被拒绝,抛SecurityException - JDK 12+ 引入了强封装默认策略(
--illegal-access=deny),此时即使调用了setAccessible(true),访问非 open 模块的内部 API 仍会失败——必须加--add-opens参数
示例:field.setAccessible(true); field.set(obj, "new value"); —— 缺少第一句,第二句必炸。
反射获取泛型类型信息要靠 getGenericXxx() 系列方法
普通 getXxxType()(如 getReturnType())返回的是擦除后的原始类型(List 而非 List<string></string>)。要拿到带泛型的实际类型,必须用 getGenericXxx() 并处理 Type 子类。
-
Method.getGenericReturnType()返回Type,可能是ParameterizedType(如List<integer></integer>)、WildcardType(如? extends Number)或TypeVariable(如T) - 数组类型要用
getGenericComponentType(),不能用getComponentType()来反推泛型 - 泛型信息只保留在源码和字节码中,运行时无法还原匿名内部类或 lambda 表达式里的类型实参
真正难的不是调用,而是安全地向下转型和递归解析嵌套泛型——比如把 Map<string list extends number>></string> 拆成三层 Type 树。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











