Java反射机制(二):动态创建对象、突破私有限制与性能剖析
在上一篇中,我们学会了如何获取 Class 对象,并验证了它在 JVM 中的唯一性。本篇将在此基础上,利用反射完成更“激进”的操作:动态创建对象(绕过 new)、调用私有方法、修改私有字段,并对比反射调用与直接调用的性能差异。通过这些实验,你将真正理解框架(如 Spring、MyBatis)底层是如何“暴力”注入依赖的。
目录
四、原理分析:setAccessible(true) 的威力
一、引言:反射还能做什么?
反射的核心能力不仅仅是拿到 Class 对象,更重要的是 “运行时操作”。在实际开发中,很多框架需要在完全不知道类结构的情况下,动态地创建对象、执行方法、甚至修改内部状态。例如:
-
Spring IoC 容器根据配置文件中的类名字符串,创建 Bean 实例。
-
JUnit 根据
@Test注解,动态调用测试方法。 -
MyBatis 将数据库查询结果自动映射到实体类的私有字段上。
这些场景都离不开反射的 Constructor、Method、Field 以及 setAccessible(true)。本篇将通过三个实验,带你亲手实现这些操作,并思考反射的代价。
二、实验准备:延续上一篇的 Student 类
我们依然使用 Student 类,它包含私有字段、公有/私有方法,方便演示。
package com.reflection.demo;
public class Student {
private String name;
private int age;
public Student() {
this.name = "默认学生";
this.age = 18;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void study() {
System.out.println(name + " 正在学习,年龄:" + age);
}
private void secret() {
System.out.println("这是一个私有方法,只能被反射调用");
}
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
}
如果还没有这个类,请参考上一篇创建。本篇所有代码均基于该类。
三、实验二:通过反射创建对象、调用私有方法、修改私有字段
新建 Experiment2.java,代码如下(完整可运行):
import com.reflection.demo.Student;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
public class Experiment2 {
public static void main(String[] args) throws Exception {
// 1. 获取 Class 对象
Class<?> clazz = Class.forName("com.reflection.demo.Student");
// 2. 通过无参构造器创建对象
Object obj1 = clazz.getDeclaredConstructor().newInstance();
System.out.println("无参构造创建的对象:" + obj1);
// 3. 通过有参构造器创建对象
Constructor<?> cons = clazz.getDeclaredConstructor(String.class, int.class);
Object obj2 = cons.newInstance("张三", 22);
System.out.println("有参构造创建的对象:" + obj2);
// 4. 调用公有方法 study
Method studyMethod = clazz.getDeclaredMethod("study");
studyMethod.invoke(obj2);
// 5. 调用私有方法 secret(暴力反射)
Method secretMethod = clazz.getDeclaredMethod("secret");
secretMethod.setAccessible(true); // 突破 private 限制
secretMethod.invoke(obj2);
// 6. 访问并修改私有字段 name
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
System.out.println("修改前的 name:" + nameField.get(obj2));
nameField.set(obj2, "李四");
System.out.println("修改后的 name:" + nameField.get(obj2));
// 再次调用 study 验证修改效果
studyMethod.invoke(obj2);
}
}
📸 截图1:Experiment2.java 代码截图

📸 截图2:运行结果

预期输出(你的实际输出应与之类似):
无参构造创建的对象:Student{name='默认学生', age=18}
有参构造创建的对象:Student{name='张三', age=22}
张三 正在学习,年龄:22
这是一个私有方法,只能被反射调用
修改前的 name:张三
修改后的 name:李四
李四 正在学习,年龄:22
四、原理分析:setAccessible(true) 的威力
-
常规编程:
private字段和方法只能在类内部访问,外部无法直接调用或修改。 -
反射机制:通过
setAccessible(true)可以 关闭 Java 语言的访问检查,从而突破封装。这被称为“暴力反射”。 -
框架中的应用:Spring 在注入依赖时,即使字段是
private的,也能直接赋值;ORM 框架将数据库列映射到实体私有字段,都依赖此特性。
⚠️ 注意:暴力反射破坏了封装性,仅在框架底层或特殊工具中使用,业务代码中应避免。
五、实验三:反射性能对比(直接调用 vs 反射调用)
反射虽然灵活,但存在性能损耗。下面通过一个循环测试来量化差距。
重要提示:为了避免运行 Experiment3 时控制台疯狂打印,请先临时修改 Student.java 中的 study() 方法,将 System.out.println(...) 注释掉或删除。实验结束后再恢复。
修改后的 study() 方法示例:
public void study() {
// System.out.println(name + " 正在学习,年龄:" + age);
}
然后新建 Experiment3.java:
import com.reflection.demo.Student;
import java.lang.reflect.Method;
public class Experiment3 {
public static void main(String[] args) throws Exception {
Student stu = new Student("测试", 20);
Method studyMethod = Student.class.getDeclaredMethod("study");
int times = 10_000_000; // 一千万次调用
// 直接调用
long start1 = System.nanoTime();
for (int i = 0; i < times; i++) {
stu.study();
}
long end1 = System.nanoTime();
// 反射调用
long start2 = System.nanoTime();
for (int i = 0; i < times; i++) {
studyMethod.invoke(stu);
}
long end2 = System.nanoTime();
System.out.println("直接调用耗时:" + (end1 - start1) / 1_000_000 + " ms");
System.out.println("反射调用耗时:" + (end2 - start2) / 1_000_000 + " ms");
}
}
📸 截图3:Experiment3.java 代码截图

📸 截图4:运行结果

典型输出(你的实际数值可能略有不同):
直接调用耗时:45 ms
反射调用耗时:320 ms
实验完成后,记得恢复 Student.java 中的 study() 方法,去掉注释。
六、性能差异原因及优化建议
| 原因 | 说明 |
|---|---|
| 动态解析 | 反射需要在运行时解析方法签名、检查访问权限、进行类型转换。 |
| JIT 编译限制 | 直接调用可以被 JIT 内联优化,反射调用则难以被优化。 |
| 安全检查 | 即使 setAccessible(true) 仍有一定开销。 |
优化建议:
-
缓存
Method、Field、Constructor对象,避免重复调用getDeclaredMethod。 -
在高频调用的场景(如循环)中避免使用反射。
-
框架层面通常会缓存反射元数据,例如 Spring 的
ReflectionUtils。
七、心得体会:反射是一把双刃剑
通过本次实验,我深刻体会到:
-
灵活性 vs 性能:反射让代码变得极其灵活,可以实现“不可能”的操作(如调用私有方法),但牺牲了运行效率。
-
封装性的突破:
setAccessible(true)虽然强大,但滥用会导致代码脆弱、难以维护。框架作者需要谨慎使用,业务开发者则应优先使用常规 API。 -
理解框架基础:Spring 的依赖注入、MyBatis 的自动映射、JUnit 的测试执行,底层都大量使用反射。掌握了反射,阅读框架源码会豁然开朗。
建议:在写工具类、框架或测试代码时可以考虑反射;普通业务逻辑中,能用
new和直接调用就尽量不用反射。
参考文献
-
Java官方文档:
java.lang.reflect包 [Online] -
CSDN博客《Java反射机制(一):深入理解Class对象》
-
Spring Framework 源码:
ReflectionUtils.java
如果本文对你有帮助,欢迎 点赞 👍 + 收藏 ⭐,你的支持是我持续分享的动力。最近在网上刷到了刘晓艳老师的一个讲座,有些观点启人以思,分享给大家。
1.卡耐基曾说过:“一个人的成功因素中,70%靠人际关系,30%靠个人努力。
2.努力不重要,机会更重要。
努力提升自己,去与优秀的人为伍,然后成为他们中的一员,大家一起变得更好。
更多推荐
所有评论(0)