面向对象编程:反射(Reflection)原理与应用详解
反射是面向对象编程中非常核心的高级特性 ,它的本质是:程序在运行时,可以动态获取类的完整信息(成员变量、方法、构造器、注解等),并且能动态调用对象的方法、操作成员变量。
简单说:正常编程是「写死代码调用类」,反射是「运行时才知道要操作哪个类,动态调用」。
一、反射的核心原理
1. 核心前提:Class 类
Java 中一切皆对象,类本身也是对象 ,每个类被加载到 JVM 后,都会生成一个唯一的 Class 对象,这个对象里保存了类的所有信息:
- 类名、父类、实现的接口
- 成员变量(Field)
- 成员方法(Method)
- 构造方法(Constructor)
- 注解、修饰符等
反射就是通过这个 Class 对象,反向获取类信息、反向调用类功能,所以叫「反射」。
2. 反射的工作流程
- 获取 Class 对象:拿到类的「元数据」入口
- 解析类信息:获取变量、方法、构造器等
- 动态操作:创建对象、调用方法、修改变量(无视权限修饰符)
3. 核心特点
✅ 运行时动态操作 :编译时不需要知道目标类,运行时才确定
✅ 无视访问权限 :可以调用 private 方法、修改 private 变量
✅ 解耦 :降低代码耦合,提高灵活性
❌ 性能较低 :比直接调用慢
❌ 破坏封装:绕过了访问控制,可能破坏代码安全性
二、反射的核心 API(Java 版)
Java 反射的核心类都在 java.lang.reflect 包下:
Class:代表一个类,反射的入口Field:代表类的成员变量Method:代表类的成员方法Constructor:代表类的构造方法
三、反射应用实战举例
我们用一个简单的实体类,演示反射的5大最常用场景。
第一步:定义测试类
java
public class User {
// 私有成员变量
private Long id;
public String username;
// 无参构造
public User() {}
// 有参构造
public User(Long id, String username) {
this.id = id;
this.username = username;
}
// 私有方法
private void privateMethod(String msg) {
System.out.println("私有方法被调用:" + msg);
}
// 公共方法
public void publicMethod() {
System.out.println("公共方法被调用");
}
// getter/setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
}
场景1:获取 Class 对象(反射入口)
3种标准方式:
java
public class ReflectTest {
public static void main(String[] args) throws ClassNotFoundException {
// 方式1:Class.forName(全类名) → 最常用(配置文件/框架底层)
Class<?> clazz1 = Class.forName("com.example.User");
// 方式2:类名.class
Class<User> clazz2 = User.class;
// 方式3:对象.getClass()
User user = new User();
Class<? extends User> clazz3 = user.getClass();
}
}
场景2:反射创建对象
java
// 1. 获取 Class 对象
Class<User> clazz = User.class;
// 2. 调用无参构造创建对象(核心)
User user = clazz.newInstance(); // 旧版
// 新版推荐:
User user1 = clazz.getDeclaredConstructor().newInstance();
System.out.println(user1); // 输出:User@xxx
// 3. 调用有参构造创建对象
Constructor<User> constructor = clazz.getConstructor(Long.class, String.class);
User user2 = constructor.newInstance(1L, "反射测试");
System.out.println(user2.getUsername()); // 输出:反射测试
场景3:反射操作成员变量(包括 private)
java
User user = new User();
Class<?> clazz = user.getClass();
// 1. 获取 public 变量
Field usernameField = clazz.getField("username");
usernameField.set(user, "张三"); // 赋值
System.out.println(user.getUsername()); // 输出:张三
// 2. 获取 private 变量(必须开启暴力反射)
Field idField = clazz.getDeclaredField("id");
idField.setAccessible(true); // 取消访问检查
idField.set(user, 100L); // 赋值
System.out.println(user.getId()); // 输出:100
场景4:反射调用成员方法(包括 private)
java
User user = new User();
Class<?> clazz = user.getClass();
// 1. 调用 public 无参方法
Method publicMethod = clazz.getMethod("publicMethod");
publicMethod.invoke(user); // 输出:公共方法被调用
// 2. 调用 private 有参方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod", String.class);
privateMethod.setAccessible(true); // 暴力反射
privateMethod.invoke(user, "我是反射调用的"); // 输出:私有方法被调用:我是反射调用的
场景5:动态获取类的全部信息
java
Class<User> clazz = User.class;
// 获取类名
System.out.println("全类名:" + clazz.getName());
System.out.println("简类名:" + clazz.getSimpleName());
// 获取所有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println("方法名:" + method.getName());
}
// 获取所有变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println("变量名:" + field.getName());
}
四、反射的实际业务/框架应用
反射不是为了日常业务代码写的 ,而是框架、中间件、工具类的底层核心技术,几乎所有主流框架都依赖反射:
1. Spring 框架(最经典)
- Spring IOC:读取 XML/注解配置的类名,用反射创建 Bean 对象
- Spring MVC:通过反射调用 Controller 里的请求方法
- AOP:动态代理 + 反射实现方法增强
2. 序列化/反序列化
- JSON 框架(Jackson、Gson、Fastjson):
不需要写 getter/setter,反射读取对象变量 → 转 JSON
JSON → 反射赋值给对象变量
3. 通用工具类
- 通用对象拷贝工具(BeanUtils.copyProperties)
- 通用 Excel 导入导出工具
- 通用参数校验工具
4. 注解解析
- 自定义注解生效:反射获取类/方法上的注解,执行对应逻辑
5. 配置驱动开发
- 读取配置文件中的类全限定名,反射实例化对象(无需硬编码)
五、反射的优缺点总结
优点
- 动态性:运行时才确定类,代码灵活、可配置
- 解耦:减少硬编码,提高扩展性
- 无视权限:框架底层需要操作私有成员
缺点
- 性能差:比直接调用慢,不适合高频调用
- 安全风险:破坏封装,可访问私有成员
- 代码复杂:可读性比直接调用差