反射
Java 反射机制详解:动态操作类的终极武器
反射(Reflection)是 Java 语言的核心特性之一,它允许程序在运行时动态获取类的信息(如属性、方法、构造器等),并能动态调用类的方法、修改属性值。这一机制为框架开发(如 Spring、MyBatis)提供了强大的灵活性,也是理解 Java 动态性的关键。本文将系统讲解反射的操作流程、核心 API 及应用场景。
反射的核心作用
在编译期,Java 代码需要明确知道类的结构才能进行操作(如 new 实例、调用方法)。而反射打破了这一限制,允许程序在运行时:
- 动态获取类的元数据(类名、父类、接口、注解等);
- 动态创建类的实例;
- 动态调用类的方法(包括私有方法);
- 动态修改类的属性(包括私有属性)。
这使得程序可以应对未知的类结构,极大地提升了代码的灵活性和扩展性。
获取 Class 对象:反射的入口
反射操作的第一步是获取目标类的 Class 对象(类的字节码对象),它是访问类元数据的入口。Java 提供了 4 种获取 Class 对象的方式:
方式一:通过类名 .class 获取
直接通过类名调用 class 属性,适用于编译期已知类名的场景:
// 获取 Person 类的 Class 对象
Class<Person> clazz = Person.class;
- 特点:无需创建实例,效率高,编译时检查类是否存在。
方式二:通过实例 .getClass() 获取
通过对象实例的 getClass() 方法获取,适用于已有实例的场景:
Person person = new Person();
Class<?> clazz = person.getClass(); // 返回 person 所属类的 Class 对象
- 特点:需要先创建实例,适用于动态获取实例的类型(如多态场景)。
方式三:通过 Class.forName() 获取
通过类的全限定名(包名 + 类名)动态加载类,适用于编译期未知类名的场景(如配置文件中指定类名):
String className = "com.example.Person"; // 类的全限定名
Class<?> clazz = Class.forName(className);
- 特点:支持动态加载,类名可来自配置文件或用户输入,若类不存在会抛出
ClassNotFoundException。
方式四:通过类加载器 loadClass() 获取
通过类加载器(ClassLoader)的 loadClass() 方法加载类,适用于自定义类加载场景:
ClassLoader classLoader = Test.class.getClassLoader(); // 获取当前类的类加载器
String className = "com.example.Person";
Class<?> clazz = classLoader.loadClass(className);
- 特点:与
Class.forName()类似,但不会执行类的静态代码块(Class.forName()会触发静态代码块执行)。
反射操作核心 API
获取 Class 对象后,可通过其提供的方法获取类的详细信息(构造器、属性、方法等),并进行动态操作。
获取构造器(Constructor)
构造器用于创建类的实例,反射可获取所有构造器(包括私有)并动态创建对象。
| 方法 | 作用 | 范围 |
|---|---|---|
getConstructors() |
获取所有公共构造器 | 本类 + 父类(仅公共) |
getConstructor(Class<?>...) |
获取指定参数的公共构造器 | 本类 |
getDeclaredConstructors() |
获取所有构造器(包括私有、保护) | 本类 |
getDeclaredConstructor(Class<?>...) |
获取指定参数的任意构造器 | 本类 |
示例代码:
Class<?> clazz = Person.class;
// 1. 获取所有公共构造器
Constructor<?>[] publicConstructors = clazz.getConstructors();
for (Constructor<?> c : publicConstructors) {
System.out.println("公共构造器:" + c);
}
// 2. 获取指定参数的私有构造器(如 Person(String name))
Constructor<?> privateConstructor = clazz.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true); // 破除访问权限限制(关键!)
Person person = (Person) privateConstructor.newInstance("张三"); // 动态创建实例
获取属性(Field)
属性包括成员变量,反射可获取所有属性(包括私有)并动态修改其值。
| 方法 | 作用 | 范围 |
|---|---|---|
getFields() |
获取所有公共属性 | 本类 + 父类(仅公共) |
getField(String name) |
获取指定名称的公共属性 | 本类 + 父类(仅公共) |
getDeclaredFields() |
获取所有属性(包括私有、保护) | 本类 |
getDeclaredField(String name) |
获取指定名称的任意属性 | 本类 |
示例代码:
Class<?> clazz = Person.class;
Person person = new Person();
// 1. 获取私有属性 "age" 并修改值
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // 允许访问私有属性
ageField.set(person, 20); // 为 person 实例的 age 属性赋值 20
// 2. 获取公共属性 "name" 并取值
Field nameField = clazz.getField("name"); // 假设 name 是 public
String name = (String) nameField.get(person); // 获取属性值
获取方法(Method)
方法是类的行为,反射可获取所有方法(包括私有)并动态调用。
| 方法 | 作用 | 范围 |
|---|---|---|
getMethods() |
获取所有公共方法 | 本类 + 父类(仅公共) |
getMethod(String name, Class<?>...) |
获取指定名称和参数的公共方法 | 本类 + 父类(仅公共) |
getDeclaredMethods() |
获取所有方法(包括私有、保护) | 本类 |
getDeclaredMethod(String name, Class<?>...) |
获取指定名称和参数的任意方法 | 本类 |
示例代码:
Class<?> clazz = Person.class;
Person person = new Person();
// 1. 调用公共方法 "setName(String)"
Method setNameMethod = clazz.getMethod("setName", String.class);
setNameMethod.invoke(person, "李四"); // 等价于 person.setName("李四")
// 2. 调用私有方法 "privateMethod()"
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true); // 允许访问私有方法
privateMethod.invoke(person); // 等价于 person.privateMethod()
获取其他元数据
除构造器、属性、方法外,Class 对象还可获取类的其他信息:
| 方法 | 作用 |
|---|---|
getName() |
获取类的全限定名(如 com.example.Person) |
getSimpleName() |
获取类的简单名(如 Person) |
getSuperclass() |
获取父类的 Class 对象 |
getInterfaces() |
获取实现的所有接口的 Class 对象数组 |
getAnnotations() |
获取类上的所有注解 |
isInterface() |
判断是否为接口 |
isEnum() |
判断是否为枚举类 |
示例代码:
Class<?> clazz = Person.class;
// 获取父类
Class<?> superClass = clazz.getSuperclass();
System.out.println("父类:" + superClass.getSimpleName());
// 获取实现的接口
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> iface : interfaces) {
System.out.println("实现的接口:" + iface.getSimpleName());
}
// 获取类上的注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation anno : annotations) {
System.out.println("类注解:" + anno);
}
反射的核心操作:动态创建与调用
反射的核心价值在于动态操作类,以下是两个典型场景:
动态创建实例
通过反射调用构造器创建实例,无需在编译期知道类名:
// 从配置文件读取类名(模拟框架场景)
String className = "com.example.Person";
Class<?> clazz = Class.forName(className);
// 调用无参构造器(要求类必须有无参构造器)
Object instance = clazz.newInstance();
// 调用有参构造器
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
Object instance2 = constructor.newInstance("张三", 20);
动态调用方法
通过反射调用方法,支持任意参数和访问权限:
Class<?> clazz = Person.class;
Object instance = clazz.newInstance();
// 动态调用方法:methodName 为方法名,params 为参数列表
String methodName = "setName";
Object[] params = {"王五"};
// 获取方法(参数类型通过 params 推断)
Class<?>[] paramTypes = new Class[params.length];
for (int i = 0; i < params.length; i++) {
paramTypes[i] = params[i].getClass();
}
Method method = clazz.getMethod(methodName, paramTypes);
// 调用方法
method.invoke(instance, params); // 等价于 instance.setName("王五")
反射的优缺点与注意事项
优点:
- 灵活性高:可动态操作未知类,适合框架开发(如 Spring 的 IOC 容器通过反射创建对象);
- 功能强大:可访问私有成员,突破类的访问权限限制。
缺点:
- 性能损耗:反射操作绕过编译期检查,需要动态解析类信息,性能比直接调用低(约慢 10~100 倍);
- 安全性风险:可访问私有成员,可能破坏类的封装性;
- 代码可读性差:反射代码较繁琐,不如直接调用直观。
注意事项:
setAccessible(true)的使用:
访问私有成员(构造器、属性、方法)时,必须调用setAccessible(true)关闭访问检查,否则会抛出IllegalAccessException。- 性能优化:
频繁使用的反射操作可通过缓存Constructor、Method、Field对象提升性能(这些对象线程安全)。 - 异常处理:
反射操作可能抛出多种受检异常(如ClassNotFoundException、NoSuchMethodException),需妥善处理。
反射的典型应用场景
- 框架开发:
- Spring IOC 容器:通过反射创建 Bean 实例,依赖注入时动态设置属性;
- MyBatis:通过反射将数据库查询结果映射为 Java 对象。
- 动态代理:
JDK 动态代理基于反射实现,通过InvocationHandler动态调用目标方法(如 AOP 切面增强)。 - 注解处理:
自定义注解(如@Controller、@RequestMapping)需通过反射获取注解信息并执行相应逻辑。 - 序列化与反序列化:
如 JSON 框架(Jackson、Gson)通过反射将 JSON 字符串转换为 Java 对象。