Constructor 构造器反射 | JavaSE

Constructor 构造器反射

一、学习目标

学完本章,你应该能够:

  • 理解 Constructor<T> 对象代表什么。
  • 区分 getConstructors()getDeclaredConstructors()
  • 区分 getConstructor()getDeclaredConstructor()
  • 根据参数类型精确获取指定构造器。
  • 获取构造器名称、参数数量、参数类型等信息。
  • 使用 Constructor.newInstance() 动态创建对象。
  • 理解反射访问非公开构造器时的访问检查机制。
  • 理解 setAccessible(true) 的用途以及 Java 21 模块系统下的边界。

二、核心知识

2.1 回顾普通对象创建

假设:

public class Dog {

    private String name;
    private int age;

    public Dog() {
    }

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

普通方式:

Dog dog1 = new Dog();

Dog dog2 = new Dog("小黑", 3);

这里:

new Dog()

实际上选择了:

Dog()

构造器。

而:

new Dog("小黑", 3)

选择:

Dog(String, int)

构造器。


2.2 反射如何看待构造器

反射将一个构造器描述为:

java.lang.reflect.Constructor

也就是说:

public Dog() {
}

在反射世界中可以获得一个对应的:

Constructor<Dog>

对象。

而:

public Dog(String name, int age) {
}

也可以获得另一个:

Constructor<Dog>

对象。

于是形成:

Dog 类
│
├── Dog()
│      ↓
│   Constructor<Dog>
│
└── Dog(String, int)
       ↓
    Constructor<Dog>

这就是反射的重要思想:

把程序结构中的“构造器”也表示成普通 Java 对象。


2.3 获取 Constructor 的第一步

仍然先获取:

Class<Dog> clazz = Dog.class;

然后:

Class
 ↓
Constructor

所以完整思路:

获取 Class
    ↓
获取 Constructor
    ↓
分析 Constructor
    ↓
调用 Constructor
    ↓
创建对象

2.4 Constructor 的主要作用

主要分成两类:

第一类:获取构造器信息

例如:

构造器叫什么?
参数有几个?
参数是什么类型?
访问修饰符是什么?
声明了什么异常?

第二类:执行构造器

即:

创建对象

对应核心方法:

constructor.newInstance(...)

所以:

反射获取构造器的最终核心目的之一,就是能够动态创建对象。


三、使用方法

3.1 准备测试类

package com.example.reflect;

public class Dog {

    private String name;
    private int age;

    private Dog() {
        this.name = "默认名称";
    }

    public Dog(String name) {
        this.name = name;
    }

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

这里存在三个构造器:

private Dog()

public Dog(String)

public Dog(String, int)

3.2 获取所有 public 构造器

使用:

getConstructors()

例如:

Class<Dog> clazz = Dog.class;

Constructor<?>[] constructors =
        clazz.getConstructors();

for (Constructor<?> constructor : constructors) {
    System.out.println(constructor);
}

这个方法获取:

当前类可见的 public 构造器。

对于上面的 Dog

private Dog()

不会出现在结果中。


3.3 获取本类声明的所有构造器

使用:

getDeclaredConstructors()

例如:

Class<Dog> clazz = Dog.class;

Constructor<?>[] constructors =
        clazz.getDeclaredConstructors();

for (Constructor<?> constructor : constructors) {
    System.out.println(constructor);
}

现在:

private Dog()
public Dog(String)
public Dog(String, int)

都能够被获取。


3.4 getConstructors 与 getDeclaredConstructors

重点对比:

| 方法 | 获取范围 | | --------------------------- | -------------------------------------- | | getConstructors() | public 构造器 | | getDeclaredConstructors() | 当前类声明的所有构造器,不论访问修饰符 |

可以记成:

没有 Declared
→ 更强调 public 可访问成员

有 Declared
→ 看当前类自己声明了什么

这里不要把:

获取到了

和:

一定可以直接调用

混为一谈。

这是后面非常重要的区别。


3.5 获取无参 public 构造器

如果类中存在:

public Dog() {
}

可以:

Constructor<Dog> constructor =
        Dog.class.getConstructor();

空参数:

getConstructor()

表示寻找:

public Dog()

3.6 获取指定参数的 public 构造器

如果:

public Dog(String name, int age) {
}

可以:

Constructor<Dog> constructor =
        Dog.class.getConstructor(
                String.class,
                int.class
        );

这里非常重要:

String.class,
int.class

描述的是构造器的:

形参类型列表。

不是传入:

"小黑",
3

因为现在做的事情不是调用构造器,而是:

找到“参数签名为 (String, int) 的那个构造器”。


3.7 获取任意可见性的指定构造器

例如类中:

private Dog() {
}

使用:

Dog.class.getConstructor();

找不到。

应该使用:

Constructor<Dog> constructor =
        Dog.class.getDeclaredConstructor();

类似:

private Dog(String name) {
}

则:

Constructor<Dog> constructor =
        Dog.class.getDeclaredConstructor(
                String.class
        );

3.8 四个核心 API 总结

                       Constructor
                            │
          ┌─────────────────┴─────────────────┐
          │                                   │
       获取多个                            获取一个
          │                                   │
   ┌──────┴──────┐                     ┌──────┴──────┐
   │             │                     │             │
public         declared              public        declared
   │             │                     │             │
getConstructors()                 getConstructor(...)
getDeclaredConstructors()         getDeclaredConstructor(...)

建议直接形成这组记忆:

getConstructors()

getDeclaredConstructors()

getConstructor(Class<?>... parameterTypes)

getDeclaredConstructor(Class<?>... parameterTypes)

3.9 获取构造器名称

Constructor<Dog> constructor =
        Dog.class.getDeclaredConstructor(
                String.class,
                int.class
        );

System.out.println(constructor.getName());

通常得到:

com.example.reflect.Dog

构造器没有普通方法那样独立的方法名。

它对应的是:

声明它的类

3.10 获取参数数量

System.out.println(
        constructor.getParameterCount()
);

对于:

Dog(String, int)

结果为:

2

3.11 获取参数类型

Class<?>[] parameterTypes =
        constructor.getParameterTypes();

for (Class<?> parameterType : parameterTypes) {
    System.out.println(parameterType.getName());
}

可能输出:

java.lang.String
int

注意:

String
int

对应的反射表示仍然是:

String.class
int.class

3.12 使用 Constructor 创建对象

假设:

public Dog(String name, int age) {
    this.name = name;
    this.age = age;
}

获取:

Constructor<Dog> constructor =
        Dog.class.getConstructor(
                String.class,
                int.class
        );

创建对象:

Dog dog =
        constructor.newInstance(
                "小黑",
                3
        );

现在:

constructor

表示:

Dog(String, int)

而:

"小黑", 3

才是真正调用构造器时传递的实参。


3.13 获取构造器和调用构造器不要混淆

第一步:

getDeclaredConstructor(
        String.class,
        int.class
);

这里传:

参数类型

表示:

我要找哪一个构造器?

第二步:

constructor.newInstance(
        "小黑",
        3
);

这里传:

实际参数

表示:

我要用什么数据创建对象?

完整模型:

String.class, int.class
        ↓
确定构造器签名
        ↓
Constructor<Dog>
        ↓
"小黑", 3
        ↓
调用构造器
        ↓
Dog 对象

3.14 无参构造器创建对象

如果:

public Dog() {
}

可以:

Constructor<Dog> constructor =
        Dog.class.getDeclaredConstructor();

Dog dog = constructor.newInstance();

注意现代 Java 中推荐:

clazz.getDeclaredConstructor().newInstance();

而不是旧代码中可能看到的:

clazz.newInstance();

后者已经被弃用。


3.15 NoSuchMethodException

假设实际只有:

Dog(String, int)

你却写:

Dog.class.getDeclaredConstructor(
        String.class
);

Java 找不到:

Dog(String)

就可能抛出:

NoSuchMethodException

所以:

参数类型必须与目标构造器签名匹配。


3.16 参数顺序也必须匹配

存在:

Dog(String name, int age)

正确:

getDeclaredConstructor(
        String.class,
        int.class
);

错误:

getDeclaredConstructor(
        int.class,
        String.class
);

因为:

(String, int)

和:

(int, String)

是完全不同的构造器签名。


3.17 private 构造器的问题

假设:

private Dog() {
}

可以获取:

Constructor<Dog> constructor =
        Dog.class.getDeclaredConstructor();

但是:

constructor.newInstance();

未必允许直接执行。

因为:

找到成员和拥有访问权限是两个不同问题。

Java 在反射调用成员时仍然存在访问检查。


3.18 setAccessible(true)

在允许的场景中可以:

constructor.setAccessible(true);

然后:

Dog dog = constructor.newInstance();

完整示例:

Constructor<Dog> constructor =
        Dog.class.getDeclaredConstructor();

constructor.setAccessible(true);

Dog dog = constructor.newInstance();

它的含义更准确地说是:

请求该反射对象在后续使用时抑制 Java 语言访问控制检查。

初学教程有时称之为:

暴力反射

这个称呼比较形象,但不要误解为:

Java 的一切访问限制都能无条件突破

3.19 trySetAccessible()

Java 还提供:

trySetAccessible()

例如:

Constructor<Dog> constructor =
        Dog.class.getDeclaredConstructor();

if (constructor.trySetAccessible()) {
    Dog dog = constructor.newInstance();
    System.out.println(dog);
} else {
    System.out.println("无法获得访问权限");
}

区别可以简单理解:

setAccessible(true)
失败时可能抛异常

trySetAccessible()
无法开放时返回 false

在需要更安全处理访问开放失败的场景中:

trySetAccessible()

会比较方便。


四、原理与进阶

4.1 setAccessible(true) 并不是万能钥匙

这是现代 Java 必须特别注意的地方。

过去很多教程会直接告诉你:

constructor.setAccessible(true);

等于:

无条件突破 private

对于普通课堂项目、同一未命名模块中的代码,这种现象经常成立。

但现代 Java 加入了:

Java Platform Module System(JPMS)

之后,反射访问还需要考虑:

模块边界
包是否 exports
包是否 opens
调用者与目标类所在模块关系

因此:

setAccessible(true)

不是:

任何 private 都一定能够强行访问

如果访问检查无法被关闭,可能出现:

InaccessibleObjectException

因此在 Java 21 中更准确的知识模型是:

private
  ↓
默认不能直接反射调用
  ↓
可以请求 setAccessible(true)
  ↓
是否成功
取决于 Java 访问规则与模块边界

4.2 为什么 Java 要限制深度反射

因为:

private

本来就是类封装设计的一部分。

如果任何程序都可以毫无限制地访问:

JDK 内部实现
框架内部私有数据
安全敏感字段

模块和封装体系就会被严重削弱。

因此现代 Java 在:

反射灵活性
vs
封装与模块安全

之间进行了更严格的平衡。


4.3 Constructor.newInstance() 可能出现哪些异常

反射创建对象远比:

new Dog()

复杂,因此可能产生多种异常。

常见包括:

NoSuchMethodException

没有找到指定构造器。


IllegalAccessException

当前调用者没有权限访问目标构造器。


InstantiationException

例如尝试实例化某些不能直接实例化的类型。


IllegalArgumentException

实际参数:

数量
类型

无法匹配目标构造器。


InvocationTargetException

非常重要。

假设构造器本身:

public User(int age) {
    if (age < 0) {
        throw new IllegalArgumentException(
                "年龄不能小于0"
        );
    }
}

通过:

constructor.newInstance(-1);

调用时,构造器内部异常通常会被反射调用机制包装到:

InvocationTargetException

中。

真正业务异常可以进一步通过:

exception.getCause()

检查。

因此反射异常处理比普通:

new User(-1)

更加复杂。


4.4 为什么不推荐 Class.newInstance()

旧教程中可能看到:

Dog dog =
        Dog.class.newInstance();

现代 Java 不推荐这种写法。

Class.newInstance() 已经被弃用。

推荐:

Dog dog =
        Dog.class
           .getDeclaredConstructor()
           .newInstance();

这样首先明确得到:

Constructor

再调用:

Constructor.newInstance()

异常语义和 API 设计更加合理。


4.5 反射创建对象与普通 new 的核心区别

普通:

Dog dog = new Dog("小黑", 3);

编译阶段:

Dog 类型
Dog 构造器
参数类型

都已经非常明确。

反射:

Class<?> clazz = Class.forName(className);

Constructor<?> constructor =
        clazz.getDeclaredConstructor(
                String.class,
                int.class
        );

Object object =
        constructor.newInstance(
                "小黑",
                3
        );

真正使用哪个类可能:

运行时才知道

因此:

普通 new
→ 编译期绑定更强

反射
→ 运行时动态性更强

五、实践应用

5.1 根据配置创建对象

配置:

className=com.example.UserService

程序:

String className =
        configuration.getProperty("className");

Class<?> clazz =
        Class.forName(className);

Constructor<?> constructor =
        clazz.getDeclaredConstructor();

Object object =
        constructor.newInstance();

现在:

Java 源代码

没有直接写:

new UserService();

类型由:

配置文件

决定。


5.2 简单对象工厂

可以封装:

public static Object createObject(
        String className
) throws ReflectiveOperationException {

    Class<?> clazz =
            Class.forName(className);

    Constructor<?> constructor =
            clazz.getDeclaredConstructor();

    return constructor.newInstance();
}

调用:

Object object =
        createObject(
            "com.example.reflect.Student"
        );

这已经开始出现:

框架通用代码

的味道。

方法并不知道:

Student
Teacher
User
Product

中的哪一个会被创建。

它只知道:

类名字符串

5.3 依赖注入为什么需要动态创建对象

未来 Spring 处理:

@Service
public class UserService {
}

框架需要做很多事情,例如:

发现类
   ↓
分析构造器
   ↓
选择构造器
   ↓
准备依赖
   ↓
创建实例
   ↓
管理对象

这个过程与现在学习的:

Class
+
Constructor

存在非常直接的思想联系。

因此这一章不是为了记:

getDeclaredConstructor()

这一个 API。

而是在理解:

框架如何把“创建对象”的控制权从业务代码手中接过去。


5.4 从 new 到框架创建对象

自己开发:

UserService service =
        new UserService();

框架开发:

扫描 / 配置
     ↓
Class
     ↓
Constructor
     ↓
newInstance
     ↓
UserService 实例

以后学习 Spring 时,你会进一步理解:

IoC(控制反转)为什么意味着对象创建权发生了转移。

这里先建立思想连接即可,不提前展开 Spring。


六、常见问题

6.1 Constructor 是构造方法本身吗?

更准确地说:

Constructor 对象是 Java 反射 API 对某个构造器的运行时表示。

通过这个对象可以:

  • 查询构造器信息。
  • 在符合访问规则的情况下调用该构造器。

6.2 getConstructors 和 getDeclaredConstructors 有什么区别?

核心:

getConstructors()
→ public

getDeclaredConstructors()
→ 当前类声明的全部构造器

6.3 为什么 getDeclaredConstructor 要传 Class?

例如:

getDeclaredConstructor(
        String.class,
        int.class
);

目的是通过:

参数类型列表

定位:

Dog(String, int)

不是在调用构造器。


6.4 为什么 newInstance 才传真正的数据?

因为:

getDeclaredConstructor(...)

解决:

找谁?

而:

newInstance(...)

解决:

用什么数据调用它?


6.5 private 构造器能不能获取?

可以通过:

getDeclaredConstructor(...)

获取其反射表示。

但:

获取到 Constructor 并不等于拥有调用权限。


6.6 setAccessible(true) 是不是一定成功?

不是。

Java 21 中还受到:

模块系统
包开放状态
访问控制

等限制。

失败时可能:

InaccessibleObjectException

6.7 为什么出现 NoSuchMethodException?

常见原因:

  • 参数数量错。
  • 参数类型错。
  • 参数顺序错。
  • 使用 getConstructor() 查找了非 public 构造器。
  • 目标类根本不存在这个构造器。

6.8 Integer.class 和 int.class 一样吗?

不一样。

假设构造器:

Dog(String name, int age)

寻找时应该:

getDeclaredConstructor(
        String.class,
        int.class
);

如果写:

Integer.class

表示的是:

java.lang.Integer

而:

int.class

表示:

基本类型 int

两者是不同类型。


6.9 构造器可以继承吗?

构造器本身:

不会被子类继承。

所以讨论:

getDeclaredConstructors()

时,本质上就是当前目标类自身声明的构造器。


七、练习与验收

7.1 知识问答

  1. Constructor 对象表示什么?
  2. 为什么获取 Constructor 之前通常先获取 Class?
  3. getConstructors() 获取什么?
  4. getDeclaredConstructors() 获取什么?
  5. getConstructor()getDeclaredConstructor() 有什么区别?
  6. 为什么获取指定构造器时传递的是 Class 类型信息?
  7. newInstance() 的作用是什么?
  8. 获取 private 构造器为什么不代表能够立即调用?
  9. setAccessible(true) 的作用是什么?
  10. Java 21 为什么不能简单理解成“setAccessible 可以无限突破 private”?
  11. 为什么现代 Java 不推荐 Class.newInstance()

7.2 代码阅读

存在:

public class User {

    private User() {
    }

    public User(String name) {
    }

    public User(String name, int age) {
    }
}

阅读:

Class<User> clazz = User.class;

Constructor<?>[] c1 =
        clazz.getConstructors();

Constructor<?>[] c2 =
        clazz.getDeclaredConstructors();

回答:

  1. c1 中理论上有哪些构造器?
  2. c2 中理论上有哪些构造器?
  3. 为什么二者数量可能不同?
  4. private 构造器是否已经“消失”?
  5. getDeclaredConstructors() 是否意味着可以直接执行 private 构造器?

7.3 手写代码

定义:

public class Person {

    private String name;
    private int age;

    private Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

要求不看资料完成:

  1. 获取全部声明构造器。

  2. 输出每个构造器:

    • 名称
    • 参数数量
    • 参数类型
  3. 获取:

    Person(String, int)
    
  4. 使用它创建:

    name = "星雨"
    age = 21
    

    的对象。


7.4 Debug

下面代码为什么可能找不到构造器?

Constructor<Person> constructor =
        Person.class.getDeclaredConstructor(
                String.class,
                Integer.class
        );

实际构造器:

public Person(String name, int age) {
}

回答:

  1. Integer.class 表示什么?
  2. int.class 表示什么?
  3. 应该如何修改?
  4. 这说明反射为什么比普通方法调用更加依赖准确的运行时类型信息?

7.5 综合训练

编写:

public static Object create(
        String className,
        Class<?>[] parameterTypes,
        Object[] arguments
)

要求:

  1. 根据 className 获取 Class
  2. 根据 parameterTypes 获取构造器。
  3. 使用 arguments 调用构造器。
  4. 返回创建出的对象。
  5. 不允许针对某个具体业务类硬编码。

例如目标效果:

Object object = create(
        "com.example.reflect.Person",
        new Class<?>[]{
                String.class,
                int.class
        },
        new Object[]{
                "星雨",
                21
        }
);

重点不是背代码,而是理解:

字符串
   ↓
Class
   ↓
Constructor
   ↓
实例

这一条动态对象创建链路。


7.6 本章验收

能够闭卷写出:

Class<Dog> clazz = Dog.class;

Constructor<?>[] constructors =
        clazz.getDeclaredConstructors();

Constructor<Dog> constructor =
        clazz.getDeclaredConstructor(
                String.class,
                int.class
        );

Dog dog =
        constructor.newInstance(
                "小黑",
                3
        );

并准确解释:

getDeclaredConstructor(...)
为什么传 Class

newInstance(...)
为什么传真实数据

getConstructor
与
getDeclaredConstructor
为什么不同

setAccessible(true)
为什么不是现代 Java 中的万能权限钥匙

如果这些都能说清楚,就真正掌握了 Constructor 反射的主干。