java反射是jvm提供的标准api,通过class对象动态操作类信息,需注意class获取方式、成员访问差异、setaccessible限制、方法调用类型匹配及类加载器隔离问题。

Java反射机制不是“黑魔法”,而是JVM在运行时暴露的一套标准API,让你能绕过编译期检查,动态获取类信息、调用方法、访问字段——但代价是性能开销和安全限制。
反射的核心入口是 Class 对象
每个类加载进JVM后都会生成唯一的 Class 实例,它是反射操作的起点。获取方式有三种:
-
String.class(最安全,编译期校验存在) -
"java.lang.String".getClass()(错误:字符串对象的getClass()返回的是String.class,不能用于任意类名) -
Class.forName("java.lang.String")(动态加载,会触发类初始化,可能抛ClassNotFoundException或ExceptionInInitializerError)
注意:Class.forName() 和 ClassLoader.loadClass() 行为不同——后者默认不初始化类,适合只想检查类是否存在但不想触发静态块的场景。
用 getDeclaredXXX() 还是 getXXX()?
这是初学者最容易混淆的点:
立即学习“Java免费学习笔记(深入)”;
-
getMethods()/getFields()只返回public成员(含父类继承的) -
getDeclaredMethods()/getDeclaredFields()返回本类声明的所有成员(包括private),但不含继承的
想访问 private 字段或方法?必须先调用 setAccessible(true)。从 Java 9 开始,模块系统默认阻止这种操作,若在模块化项目中使用,需在 module-info.java 中显式声明 opens 或 exports。
反射调用方法时的参数类型匹配很严格
Method.invoke(obj, args...) 看似简单,但实际容易因类型不匹配失败:
- 基本类型参数必须传对应包装类或原始值(如
int可传42或Integer.valueOf(42)),但不能传Long给期望int的参数 - 可变参数(
String...)要传Object[]数组,不能直接传多个String - 如果目标方法抛出受检异常,
invoke()会把它包在InvocationTargetException中抛出,需用getCause()提取原始异常
示例:method.invoke(obj, new Object[]{new String[]{"a", "b"}}) 才能正确匹配 void f(String...);传 "a", "b" 会被当成两个独立参数,导致 IllegalArgumentException。
动态加载类要注意类加载器隔离
同一个类名,被不同 ClassLoader 加载后,JVM 视为完全不同的类型,无法强制转换:
- 自定义
URLClassLoader加载的MyService和系统类加载器加载的MyService不兼容 - 常见错误:
ClassCastException出现在看似合理的转型语句上,根源是类加载器不一致 - 解决办法:统一使用某个类加载器(比如当前线程上下文类加载器
Thread.currentThread().getContextClassLoader()),或通过接口解耦(让动态类实现一个由启动类加载器加载的接口)
Spring、Tomcat、OSGi 的插件机制都重度依赖这点——但也是线上 NoClassDefFoundError 和 LinkageError 最常发生的温床。










