跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

什么是Java的反射机制(Reflection)_运行时动态获取类信息的原理

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
的区别在于前者不初始化类,适合延迟加载或避免副作用。 立即学习 “ Java免费学习笔记(深入) ”; Eclipse导入Android或其他的JAVA项目的正确方法 WORD版 本文档主要讲述的是Eclipse导入Android或其他的JAVA项目的正确方法;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看 下载 调用私有方法/字段前必须调用 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
)。要拿到带泛型的实际类型,必须用
getGenericXxx()
并处理
Type
子类。
Method.getGenericReturnType()
返回
Type
,可能是
ParameterizedType
(如
List
)、
WildcardType
(如
? extends Number
)或
TypeVariable
(如
T
) 数组类型要用
getGenericComponentType()
,不能用
getComponentType()
来反推泛型 泛型信息只保留在源码和字节码中,运行时无法还原匿名内部类或 lambda 表达式里的类型实参 真正难的不是调用,而是安全地向下转型和递归解析嵌套泛型——比如把
Map>
拆成三层
Type
树。 反射的复杂性不在语法,而在它把编译期约束全推到了运行时:类是否存在、是否可访问、泛型是否完整、模块是否开放……每一步都可能断裂,而且错误信息往往不指向真正原因。

相关文章