Java 反射(Reflection)(超详细)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观
1. 什么是 Java 反射(Reflection)?
Java 反射(Reflection)是一种让程序在运行时能够动态获取自身结构、操作对象属性或调用方法的技术。它就像是给程序装上了一面“X光镜”,开发者可以通过这面镜子看到程序内部的类、方法、字段等细节,并在运行时根据需要进行灵活调整。
形象比喻:
想象你正在拆解一台复杂的机器,而反射就是一把“万能螺丝刀”。无论这台机器有多少隐藏的零件,这把螺丝刀都能帮你找到并操作它们,即使这些零件在设计时并未被直接暴露出来。
2. 为什么需要反射?
2.1 动态性与灵活性
在编译时无法确定的场景下,反射能帮助程序在运行时动态加载类、创建对象或调用方法。例如:
- 框架开发:Spring 框架通过反射自动装配 Bean。
- 插件系统:动态加载外部 JAR 包中的类。
- 单元测试:模拟或测试私有方法。
2.2 突破封装限制
虽然 Java 的封装性(如 private
关键字)旨在保护代码结构,但反射能绕过这些限制,直接访问或修改私有成员。例如:
// 假设有一个类 Person,其私有字段 name
public class Person {
private String name;
// ...
}
// 通过反射修改私有字段
Field nameField = Person.class.getDeclaredField("name");
nameField.setAccessible(true); // 强制访问
nameField.set(personInstance, "New Name");
2.3 元数据查询
反射能获取类的元数据(如方法名、注解信息),这对需要依赖“类信息本身”的场景至关重要。例如:
- 序列化框架:如 Jackson 通过反射解析对象属性与 JSON 字段的映射关系。
- 依赖注入:通过注解
@Autowired
定位需要注入的 Bean。
3. 反射的核心概念与操作步骤
3.1 获取 Class 对象
所有反射操作都始于 Class
对象,它是类的“蓝图”。获取它的方法有三种:
- 通过对象的
getClass()
方法:Object obj = new Object(); Class<?> objClass = obj.getClass();
- 通过类名的
.class
后缀:Class<?> stringClass = String.class;
- 通过
Class.forName()
静态方法:Class<?> listClass = Class.forName("java.util.ArrayList");
3.2 动态创建对象
反射允许在运行时通过 newInstance()
方法创建对象,但需注意:
- 构造方法必须是公共的(否则需通过
setAccessible(true)
强制访问)。 - Java 9 后推荐使用
getDeclaredConstructor().newInstance()
,以支持私有构造方法。
// 动态创建 Person 对象
Class<?> personClass = Class.forName("com.example.Person");
Constructor<?> constructor = personClass.getDeclaredConstructor(String.class);
constructor.setAccessible(true); // 允许访问私有构造方法
Object person = constructor.newInstance("John Doe");
3.3 访问字段与方法
字段操作
通过 getFields()
和 getDeclaredFields()
可分别获取公共字段和所有字段:
Field[] publicFields = Person.class.getFields(); // 只获取 public 字段
Field[] allFields = Person.class.getDeclaredFields(); // 获取所有字段(包括 private)
方法操作
通过 getMethods()
和 getDeclaredMethods()
获取方法:
// 获取并调用 public 方法
Method toStringMethod = Person.class.getMethod("toString");
String result = (String) toStringMethod.invoke(personInstance);
// 获取私有方法并调用
Method privateMethod = Person.class.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true);
privateMethod.invoke(personInstance);
3.4 处理注解
反射可以读取类、方法或字段上的注解信息:
// 获取类上的注解
Annotation[] annotations = Person.class.getAnnotations();
// 获取方法上的注解
Method method = Person.class.getMethod("setName", String.class);
Annotation annotation = method.getAnnotation(MyAnnotation.class);
4. 反射的典型应用场景与案例
4.1 案例 1:动态调用私有方法
假设有一个类 Calculator
,其私有方法 private int multiply(int a, int b)
需要被外部调用:
public class Calculator {
private int multiply(int a, int b) {
return a * b;
}
}
// 通过反射调用
Method multiplyMethod = Calculator.class.getDeclaredMethod("multiply", int.class, int.class);
multiplyMethod.setAccessible(true);
int result = (int) multiplyMethod.invoke(calculatorInstance, 5, 3); // result = 15
4.2 案例 2:实现简单工厂模式
反射可以动态创建对象,无需硬编码类名:
public class DynamicFactory {
public static Object createInstance(String className)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> clazz = Class.forName(className);
return clazz.newInstance();
}
}
4.3 案例 3:解析 JSON 数据
假设需要将 JSON 字符串转换为对象,但字段名与类属性名不一致:
public class User {
private String fullName; // JSON 中的字段名为 "full_name"
// ...
}
// 通过反射设置字段值
Field field = User.class.getDeclaredField("fullName");
field.setAccessible(true);
field.set(userInstance, jsonObject.getString("full_name"));
5. 反射的局限性与性能问题
5.1 安全性风险
反射可能破坏封装性,例如绕过 private
权限修改敏感数据,需谨慎使用。
5.2 性能开销
反射操作涉及动态查找方法、检查权限等,比直接调用慢 10~100 倍。建议:
- 缓存 Class 对象或 Method 对象:
private static final Method METHOD = getMethodOnce();
- 避免在循环中使用反射:
// ❌ 不推荐 for (Object obj : list) { method.invoke(obj); // 每次调用反射 } // ✅ 优化后 Method cachedMethod = ... // 提前缓存 for (Object obj : list) { cachedMethod.invoke(obj); // 直接使用缓存 }
6. 反射与动态代理
Java 的动态代理(Dynamic Proxy)是反射的延伸应用,允许在运行时为接口创建代理对象。例如:
// 定义接口
interface Service {
void execute();
}
// 创建代理对象
Service proxy = (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class<?>[]{Service.class},
(proxyObj, method, args) -> {
System.out.println("Before method");
method.invoke(realService, args);
System.out.println("After method");
return null;
}
);
7. 总结:合理使用反射的黄金法则
反射是一把“双刃剑”,需遵循以下原则:
- 优先使用常规方法:除非动态性需求不可替代。
- 避免过度暴露内部细节:合理设置类的可见性。
- 性能优化第一:缓存反射对象并减少高频调用。
- 结合注解与框架:如使用 Lombok 的
@NoArgsConstructor
替代手动反射构造对象。
通过反射,开发者能突破静态编译的限制,但需始终以代码的可维护性和效率为最高目标。
关键词布局检查:
- 文章中通过“Java 反射”“反射”“动态操作”等自然融入核心关键词,满足 SEO 要求。
- 案例与代码示例覆盖了反射的核心功能,确保内容实用性。