1. Creating Objects via Reflection
有时我们在编译期并不知道类型却需要创建对象。例如,类名来自配置,或者我们在编写通用框架(序列化器、DI 容器)。在常规代码中我们会写:
User user = new User("Ivan", 25);
但如果我们不知道类叫 User,也不知道它构造函数的参数呢?这时反射就能帮上忙。
过时的方法:Class.newInstance()
过去 Java 中有方法 Class.newInstance(),通过无参的 public 构造器创建对象:
Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.newInstance();
重要:该方法自 Java 9 起被标记为已弃用,不再推荐使用。它无法选择构造器、会掩盖错误原因,并且仅能调用无参的 public 构造器。
现代方法:通过构造器
在真实代码中,未必总有无参的 public 构造器。推荐做法是通过 getConstructor(...) 或 getDeclaredConstructor(...) 获取需要的构造器,然后调用它的 newInstance(...)。
示例:带参数地创建对象
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
import java.lang.reflect.Constructor;
Class<?> clazz = Class.forName("User");
// 获取带有参数 String、int 的构造器
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// 创建对象并传入参数
Object user = constructor.newInstance("Ivan", 25);
System.out.println(user); // 如果定义了 toString
这里发生了什么?
- 按签名获取所需构造器。
- 调用它的 newInstance(...),传入参数。
- 得到一个 Object 类型的对象(如果已知类型,可强转为 User)。
如果构造器是私有的?
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // 魔法!现在可以调用私有构造器了。
Object user = constructor.newInstance("Ivan", 25);
注意:不要滥用——这会破坏封装性。在模块化应用(Java 9+)中还会受到访问限制。
对象创建方式对比表
| 方式 | 性能 | 类型安全 | 适用场景 |
|---|---|---|---|
|
最快 | 完全 | 能用就用 |
|
较慢 | 丢失 | 框架、插件 |
|
中等 | 部分 | 高性能库 |
| 工厂 | 快速 | 完全 | 灵活地创建对象 |
2. 通过反射调用方法
你可以调用对象的任意方法,即使在编译期不知道其名称,或即使它是 private。
获取方法
import java.lang.reflect.Method;
Class<?> clazz = user.getClass();
// 按名称与参数类型获取 public 方法
Method method = clazz.getMethod("getName"); // 无参数
Object result = method.invoke(user); // 调用无参方法
System.out.println(result); // 将输出用户的名字
调用带参数的方法
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "Petr"); // 设置新名字
调用私有方法
Method secret = clazz.getDeclaredMethod("secretMethod", int.class);
secret.setAccessible(true); // 取消限制
Object secretResult = secret.invoke(user, 123);
重要:所有参数以对象数组(可变参数)的形式传入。若方法有返回值——结果将以 Object 返回。
3. 通过反射访问字段
有时需要读取或修改字段,即便它是 private(例如在序列化或测试时)。
获取字段
import java.lang.reflect.Field;
Class<?> clazz = user.getClass();
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // 如果字段不是 public
// 读取值
Object age = ageField.get(user);
System.out.println("年龄: " + age);
// 修改值
ageField.set(user, 42);
System.out.println("新年龄: " + ageField.get(user));
处理静态字段
如果字段是静态的,在 get/set 中把对象参数传 null:
Field staticField = clazz.getDeclaredField("counter");
staticField.setAccessible(true);
staticField.set(null, 100); // 对于静态字段,不需要实例对象
4. 限制与异常
使用反射可能伴随许多受检异常。最常见的有:
- ClassNotFoundException — 找不到该类名对应的类。
- NoSuchMethodException — 不存在该签名的构造器或方法。
- NoSuchFieldException — 找不到字段。
- IllegalAccessException — 无权访问成员(例如对私有方法未调用 setAccessible(true))。
- InstantiationException — 类是抽象类或接口;无法创建对象。
- InvocationTargetException — 被调用的构造器或方法内部抛出了异常。
处理示例:
try {
// ... 你的反射代码 ...
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
如果不关心细节,捕获它们的公共父类 ReflectiveOperationException(Java 7+)会更方便。
5. 实战:迷你程序“动态实例化器”
用于实验的示例类
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private void sayHello() {
System.out.println("你好,我叫 " + name + ",我 " + age + " 岁。");
}
}
动态创建与操作
import java.lang.reflect.*;
public class ReflectionDemo {
public static void main(String[] args) {
try {
// 1. 通过类名获取 Class 对象
Class<?> clazz = Class.forName("Person");
// 2. 获取构造器并创建对象
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object person = constructor.newInstance("Alice", 30);
// 3. 调用私有方法 sayHello
Method sayHello = clazz.getDeclaredMethod("sayHello");
sayHello.setAccessible(true);
sayHello.invoke(person); // 输出:你好,我叫 Alice,我 30 岁。
// 4. 修改私有字段 name
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(person, "Bob");
// 5. 再次调用 sayHello
sayHello.invoke(person); // 输出:你好,我叫 Bob,我 30 岁。
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}
请注意:
- 无需在代码中直接知道 Person 的类型。
- 可以修改私有字段并调用私有方法(前提是 JVM 的安全策略和模块配置允许)。
6. 这与真实应用有什么关系?
反射是许多流行库和框架的基础:
- JUnit:查找带注解 @Test 的方法、创建测试实例并调用方法。
- Spring:DI、创建 bean、自动装配。
- Jackson、Gson:对象字段的序列化/反序列化。
- Hibernate:访问实体字段、代理与延迟加载。
7. 可视化流程:对象如何通过反射被创建
flowchart TB
A["类名(String)"] --> B["Class.forName"]
B --> C["Class<?>"]
C --> D["getConstructor(...)"]
D --> E["Constructor<?>"]
E --> F["newInstance(...)"]
F --> G["Object"]
8. 使用反射的常见错误
错误 1:构造器不匹配。 按错误的签名去获取构造器,例如只存在带两个参数的构造器却调用 getConstructor(String.class),会得到 NoSuchMethodException。务必核对签名!
错误 2:无访问权限。 调用私有构造器/方法而不执行 setAccessible(true) 会导致 IllegalAccessException。并且请记住:Java 9+ 的模块系统可能施加额外限制。
错误 3:类型问题。 所有通过反射传递的参数与返回值都是 Object。错误的强制转换会产生 ClassCastException。
错误 4:未处理的异常。 反射会抛出很多受检异常。请进行处理,例如捕获通用的 ReflectiveOperationException,否则代码无法编译。
错误 5:破坏封装性。 轻率地使用 setAccessible(true) 就像“擅自打开别人的冰箱”。仅在确有必要时使用,并考虑安全要求。
GO TO FULL VERSION