反射(Reflection)
Java 是静态语言,但通过反射机制可以在运行时动态获取类信息、创建对象、调用方法,使 Java 具备"准动态语言"的特性。
动态语言:在运行时能改变程序自身的结构(如 JavaScript、Python)。
核心原理:
- 一个类在内存中只有一个 Class 对象
- 类被加载后,整个结构都被封装到该 Class 对象中
- 反射就是通过 Class 对象反向获取类的所有信息
Class 类
对于每个类,JRE 都为其保留一个不变的 Class 类型对象,包含该类的所有结构信息。
- Class 本身也是对象
- 只能由系统(类加载器)创建,不能手动 new
- 一个加载的类在 JVM 中只有一个 Class 实例
- 每个 Class 对象对应一个
.class文件 - 每个类的实例都记得自己由哪个 Class 实例生成
- Class 类是反射的根源,一切反射操作都从获取 Class 对象开始
常用方法
| 方法 | 说明 |
|---|---|
static Class<?> forName(String name) |
根据全限定名返回 Class 对象 |
T newInstance() |
调用无参构造创建实例(JDK 9 已废弃,推荐用 Constructor.newInstance) |
String getName() |
返回全限定类名 |
String getSimpleName() |
返回简单类名(不含包名) |
String getPackageName() |
返回包名 |
Class<?> getSuperclass() |
获取父类的 Class 对象 |
Class<?>[] getInterfaces() |
获取实现的接口 |
int getModifiers() |
获取修饰符(通过 Modifier 工具类解析) |
ClassLoader getClassLoader() |
获取类加载器 |
Constructor<?>[] getConstructors() |
获取所有 public 构造器 |
Constructor<?>[] getDeclaredConstructors() |
获取所有构造器(含私有) |
Method[] getMethods() |
获取所有 public 方法(含继承的) |
Method[] getDeclaredMethods() |
获取本类声明的所有方法(不含继承) |
Method getMethod(String name, Class<?>... params) |
获取指定 public 方法 |
Method getDeclaredMethod(String name, Class<?>... params) |
获取本类声明的指定方法(含私有) |
Field[] getFields() |
获取所有 public 字段 |
Field[] getDeclaredFields() |
获取本类声明的所有字段(含私有) |
Field getField(String name) |
获取指定 public 字段 |
Field getDeclaredField(String name) |
获取本类声明的指定字段(含私有) |
Annotation[] getAnnotations() |
获取所有注解 |
<A extends Annotation> A getAnnotation(Class<A> c) |
获取指定注解 |
boolean isAnnotationPresent(Class<? extends Annotation> c) |
判断是否有某注解 |
boolean isInterface() / isArray() / isPrimitive() |
判断类型 |
Type[] getGenericInterfaces() |
获取带泛型的接口信息 |
Type getGenericSuperclass() |
获取带泛型的父类信息 |
getFields vs getDeclaredFields 区别
getXxx():只返回 public 成员(含继承的)getDeclaredXxx():返回本类声明的所有成员(含私有),不含继承
获取 Class 对象的五种方式
// 1. 类名.class —— 最安全可靠,性能最高
Class<Person> clazz1 = Person.class;
// 2. 实例.getClass()
Class<?> clazz2 = person.getClass();
// 3. Class.forName("全限定名") —— 最常用,运行时动态加载
Class<?> clazz3 = Class.forName("com.example.Person");
// 4. 基本类型的 TYPE 属性
Class<Integer> clazz4 = Integer.TYPE; // 等价于 int.class
// 5. 通过 ClassLoader
Class<?> clazz5 = ClassLoader.getSystemClassLoader().loadClass("com.example.Person");
操作 Field(字段)
Class<?> clazz = Class.forName("com.example.Person");
// 获取 public 字段
Field nameField = clazz.getField("name");
// 获取私有字段
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // 绕过访问检查
// 获取字段信息
String fieldName = ageField.getName();
Class<?> fieldType = ageField.getType();
int modifiers = ageField.getModifiers();
boolean isPrivate = Modifier.isPrivate(modifiers);
// 读写字段值
Object obj = clazz.getDeclaredConstructor().newInstance();
nameField.set(obj, "张三"); // 写
String value = (String) nameField.get(obj); // 读
// 静态字段读写(传 null)
Field staticField = clazz.getDeclaredField("COUNT");
staticField.set(null, 100);
操作 Method(方法)
Class<?> clazz = Class.forName("com.example.Person");
Object obj = clazz.getDeclaredConstructor().newInstance();
// 获取无参方法
Method sayHello = clazz.getDeclaredMethod("sayHello");
sayHello.invoke(obj);
// 获取带参方法
Method setName = clazz.getDeclaredMethod("setName", String.class);
setName.invoke(obj, "李四");
// 获取私有方法
Method privateMethod = clazz.getDeclaredMethod("secretMethod");
privateMethod.setAccessible(true);
privateMethod.invoke(obj);
// 获取静态方法
Method staticMethod = clazz.getDeclaredMethod("staticMethod", int.class);
staticMethod.invoke(null, 42); // 静态方法传 null
// 获取返回值
Method getAge = clazz.getDeclaredMethod("getAge");
int age = (int) getAge.invoke(obj);
操作 Constructor(构造器)
Class<?> clazz = Class.forName("com.example.Person");
// 获取无参构造
Constructor<?> noArgCons = clazz.getDeclaredConstructor();
Object obj1 = noArgCons.newInstance();
// 获取带参构造
Constructor<?> paramCons = clazz.getDeclaredConstructor(String.class, int.class);
Object obj2 = paramCons.newInstance("王五", 25);
// 获取私有构造(如单例模式)
Constructor<?> privateCons = clazz.getDeclaredConstructor(String.class);
privateCons.setAccessible(true);
Object obj3 = privateCons.newInstance("秘密");
// 获取构造器参数信息
Class<?>[] paramTypes = paramCons.getParameterTypes();
int paramCount = paramCons.getParameterCount();
setAccessible 与安全检查
setAccessible(true)可以绕过 Java 的访问控制检查- 必须在有足够权限时才能使用,否则抛出
SecurityException - JDK 9+ 模块化系统下,跨模块访问非公开 API 需要额外
--add-opens参数 - 使用后记得恢复:
field.setAccessible(false)
// JDK 9+ 跨模块反射需要 VM 参数
// --add-opens java.base/java.lang=ALL-UNNAMED
注解反射
Class<?> clazz = MyService.class;
// 判断类上是否有某注解
if (clazz.isAnnotationPresent(Service.class)) {
Service anno = clazz.getAnnotation(Service.class);
String name = anno.value(); // @Service("userService")
}
// 获取方法上的注解
Method method = clazz.getDeclaredMethod("doSomething");
if (method.isAnnotationPresent(Transactional.class)) {
Transactional tx = method.getAnnotation(Transactional.class);
boolean readOnly = tx.readOnly();
}
// 获取字段上的注解
Field field = clazz.getDeclaredField("id");
Id idAnno = field.getAnnotation(Id.class);
// 获取参数的注解
for (Parameter param : method.getParameters()) {
if (param.isAnnotationPresent(Param.class)) {
String paramName = param.getAnnotation(Param.class).value();
}
}
泛型反射
Java 泛型在编译后会擦除,但通过反射可以获取部分泛型信息。
// 获取方法参数泛型
Method method = clazz.getMethod("setList", List.class);
Type[] genericParamTypes = method.getGenericParameterTypes();
for (Type type : genericParamTypes) {
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Type[] actualTypes = pt.getActualTypeArguments();
// actualTypes[0] 就是 List<String> 中的 String
}
}
// 获取方法返回值泛型
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
Type[] args = ((ParameterizedType) genericReturnType).getActualTypeArguments();
}
// 获取字段泛型
Field listField = clazz.getDeclaredField("dataList");
Type genericType = listField.getGenericType();
if (genericType instanceof ParameterizedType) {
Type[] args = ((ParameterizedType) genericType).getActualTypeArguments();
}
// 获取父类泛型(常用于 DAO 基类)
Type superType = clazz.getGenericSuperclass();
if (superType instanceof ParameterizedType) {
Type entityType = ((ParameterizedType) superType).getActualTypeArguments()[0];
}
数组反射
// 创建数组
int[] arr = (int[]) Array.newInstance(int.class, 10);
String[] strArr = (String[]) Array.newInstance(String.class, 5);
// 操作数组元素
Array.set(arr, 0, 42);
Array.set(strArr, 0, "hello");
int val = (int) Array.get(arr, 0);
// 判断是否为数组
Class<?> arrClass = int[].class;
boolean isArr = arrClass.isArray();
Class<?> componentType = arrClass.getComponentType(); // int.class
// 多维数组
int[][] matrix = (int[][]) Array.newInstance(int.class, 3, 4);
动态代理
反射的核心应用之一,在运行时动态创建代理类。
// JDK 动态代理(基于接口)
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after: " + method.getName());
return result;
}
}
// 创建代理对象
MyService target = new MyServiceImpl();
MyService proxy = (MyService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyInvocationHandler(target)
);
proxy.doSomething(); // 前后自动打印日志
要求:被代理类必须实现接口。如无需接口可用 CGLIB(字节码层面代理子类)。
性能考虑
- 反射调用比直接调用慢,大约差 10-100 倍
setAccessible(true)也有额外开销
优化建议:
- 缓存 Class / Method / Field 对象,避免重复查找
- 能用
getMethod就别用getDeclaredMethod(查找范围更小) - 批量处理时关闭安全检查(
setAccessible一次性设置) - 高频调用考虑用
MethodHandle(JDK 7+)或 Lambda 替代 - 框架层面常用反射,业务代码尽量直接调用
常见应用场景
| 场景 | 说明 |
|---|---|
| Spring IoC | 通过反射创建 Bean、注入依赖 |
| ORM 框架 | 反射读取实体类字段,映射数据库列 |
| 序列化/反序列化 | Jackson、Gson 通过反射读写对象属性 |
| 动态代理 | AOP 切面编程、RPC 远程调用 |
| 注解处理器 | 运行时扫描注解并执行对应逻辑 |
| 单元测试 | JUnit 通过反射调用 @Test 方法 |
| 插件化 | 动态加载 jar 中的类 |
| IDE 功能 | 代码补全、重构等依赖反射获取类信息 |
反射的优缺点
优点:
- 运行时动态创建对象和调用方法,灵活性极高
- 是框架设计的基石(Spring、MyBatis 等严重依赖反射)
缺点:
- 性能开销大
- 破坏封装性,可以访问私有成员
- 编译期不会检查类型安全,错误推迟到运行时
- 代码可读性降低
JDK 版本差异
- JDK 9:
Class.newInstance()废弃,用Constructor.newInstance()替代 - JDK 9+:模块化系统限制了跨模块反射,需要在
module-info.java中opens或添加 VM 参数 - JDK 17+:强封装 JDK 内部 API,反射访问内部类需要
--add-opens - 推荐使用
MethodHandle和VarHandle(JDK 7/9)作为反射的更高性能替代