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 知识问答
Constructor对象表示什么?- 为什么获取 Constructor 之前通常先获取 Class?
getConstructors()获取什么?getDeclaredConstructors()获取什么?getConstructor()与getDeclaredConstructor()有什么区别?- 为什么获取指定构造器时传递的是
Class类型信息? newInstance()的作用是什么?- 获取 private 构造器为什么不代表能够立即调用?
setAccessible(true)的作用是什么?- Java 21 为什么不能简单理解成“setAccessible 可以无限突破 private”?
- 为什么现代 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();
回答:
c1中理论上有哪些构造器?c2中理论上有哪些构造器?- 为什么二者数量可能不同?
- private 构造器是否已经“消失”?
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;
}
}
要求不看资料完成:
-
获取全部声明构造器。
-
输出每个构造器:
- 名称
- 参数数量
- 参数类型
-
获取:
Person(String, int) -
使用它创建:
name = "星雨" age = 21的对象。
7.4 Debug
下面代码为什么可能找不到构造器?
Constructor<Person> constructor =
Person.class.getDeclaredConstructor(
String.class,
Integer.class
);
实际构造器:
public Person(String name, int age) {
}
回答:
Integer.class表示什么?int.class表示什么?- 应该如何修改?
- 这说明反射为什么比普通方法调用更加依赖准确的运行时类型信息?
7.5 综合训练
编写:
public static Object create(
String className,
Class<?>[] parameterTypes,
Object[] arguments
)
要求:
- 根据
className获取Class。 - 根据
parameterTypes获取构造器。 - 使用
arguments调用构造器。 - 返回创建出的对象。
- 不允许针对某个具体业务类硬编码。
例如目标效果:
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 反射的主干。