CodeGym /课程 /JAVA 25 SELF /Creating Objects via Reflection

Creating Objects via Reflection

JAVA 25 SELF
第 62 级 , 课程 3
可用

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+)中还会受到访问限制。

对象创建方式对比表

方式 性能 类型安全 适用场景
new User()
最快 完全 能用就用
Constructor.newInstance()
较慢 丢失 框架、插件
MethodHandle
中等 部分 高性能库
工厂 快速 完全 灵活地创建对象

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 的方法、创建测试实例并调用方法。
  • SpringDI、创建 bean、自动装配。
  • JacksonGson:对象字段的序列化/反序列化。
  • 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) 就像“擅自打开别人的冰箱”。仅在确有必要时使用,并考虑安全要求。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION