这是一个非常核心且强大的反射功能,它允许我们在运行时绕过 Java 的访问控制修饰符(如 private),但同时,它也破坏了封装性,带来了安全风险,因此需要谨慎使用。

核心思想
Java 的访问控制(如 private)是在编译时由 Java 编译器强制执行的,而反射机制是在运行时工作的,它通过 java.lang.reflect 包中的 API 来访问和操作类的内部结构,反射 API 提供了一系列方法来“暴力”地突破编译时的限制。
访问 private 字段
假设我们有这样一个类 Person,它有一个 private 字段 name。
步骤 1: 定义一个带有 private 字段的类
// Person.java
package com.example.reflect;
public class Person {
private String name;
private int age;
public Person() {
// 默认构造函数
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 为了方便打印,我们重写 toString 方法
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
步骤 2: 使用反射获取并修改 private 字段
import java.lang.reflect.Field;
public class Main {
public static void main(String[] args) throws Exception {
// 1. 创建 Person 对象实例
Person person = new Person("Alice", 30);
// 2. 获取 Person 类的 Class 对象
Class<?> personClass = person.getClass();
// 3. 获取 name 字段的 Field 对象
// 如果字段名错误,会抛出 NoSuchFieldException
Field nameField = personClass.getDeclaredField("name");
// 4. 【关键步骤】设置可访问性,忽略访问修饰符检查
// 这就是突破 private 限制的核心!
nameField.setAccessible(true);
// 5. 获取字段值
// get(Object obj) 方法:获取指定对象 obj 上此字段的值
String currentName = (String) nameField.get(person);
System.out.println("修改前的 name: " + currentName); // 输出: 修改前的 name: Alice
// 6. 修改字段值
// set(Object obj, Object value) 方法:将指定对象 obj 上此字段的值设置为 value
nameField.set(person, "Bob");
// 7. 再次获取并打印,验证修改是否成功
System.out.println("修改后的 name: " + person); // 输出: 修改后的 name: Person{name='Bob', age=30}
System.out.println("通过反射获取的 name: " + nameField.get(person)); // 输出: 通过反射获取的 name: Bob
}
}
核心代码解析:
-
personClass.getDeclaredField("name"):
(图片来源网络,侵删)getField(String name):只能获取public的字段(包括父类的)。getDeclaredField(String name):可以获取本类中所有声明的字段,包括private,protected,default修饰的,但不包括父类的,这是我们获取private字段的关键。
-
nameField.setAccessible(true):- 这是最关键的一步,这个方法来自
java.lang.reflect.AccessibleObject类,是Field,Method,Constructor的父类。 - 它的作用是取消 Java 语言的访问检查,一旦设置为
true,反射对象就可以在后续的调用中忽略访问修饰符的限制。 - 这个方法通常被称为“暴力反射”或“反射暴力破解”。
- 这是最关键的一步,这个方法来自
调用 private 方法
过程与访问字段类似,只是使用的是 Method 类。
步骤 1: 定义一个带有 private 方法的类
// Person.java
package com.example.reflect;
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
// 这是一个 private 方法
private void greet() {
System.out.println("Hello, my name is " + this.name);
}
}
步骤 2: 使用反射调用 private 方法
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception {
// 1. 创建 Person 对象实例
Person person = new Person("Charlie");
// 2. 获取 Person 类的 Class 对象
Class<?> personClass = person.getClass();
// 3. 获取 greet 方法的 Method 对象
// 需要指定方法名和参数类型列表
Method greetMethod = personClass.getDeclaredMethod("greet");
// 4. 【关键步骤】设置可访问性
greetMethod.setAccessible(true);
// 5. 调用方法
// invoke(Object obj, Object... args) 方法
// obj: 调用该方法的对象实例
// args: 方法的参数,如果没有参数则不传或传 null
System.out.println("准备调用 private greet() 方法...");
greetMethod.invoke(person); // 执行 person.greet()
System.out.println("private greet() 方法调用完毕。");
}
}
输出:
准备调用 private greet() 方法...
Hello, my name is Charlie
private greet() 方法调用完毕。
调用 private 构造函数
这通常用于创建单例模式类的实例,或者访问外部包中类的 private 构造函数。

步骤 1: 定义一个带有 private 构造函数的类
// Singleton.java
package com.example.reflect;
public class Singleton {
// 私有构造函数,防止外部实例化
private Singleton() {
System.out.println("Singleton 私有构造函数被调用了!");
}
public void doSomething() {
System.out.println("Singleton is doing something.");
}
}
步骤 2: 使用反射调用 private 构造函数
import java.lang.reflect.Constructor;
public class Main {
public static void main(String[] args) throws Exception {
// 1. 获取 Singleton 类的 Class 对象
Class<?> singletonClass = Singleton.class;
// 2. 获取私有构造函数
// getDeclaredConstructor(Class<?>... parameterTypes)
// 如果是无参构造函数,则不传参数
Constructor<?> privateConstructor = singletonClass.getDeclaredConstructor();
// 3. 【关键步骤】设置可访问性
privateConstructor.setAccessible(true);
// 4. 调用构造函数创建新实例
// newInstance(Object... args) 方法
// 如果是无参构造函数,则不传参数
Singleton singletonInstance = (Singleton) privateConstructor.newInstance();
// 5. 使用新创建的实例
singletonInstance.doSomething();
}
}
输出:
Singleton 私有构造函数被调用了!
Singleton is doing something.
调用 private 静态方法
调用静态方法时,invoke 方法的第一个参数 obj 应该为 null。
// Utils.java
package com.example.reflect;
public class Utils {
private static void printStaticMessage() {
System.out.println("这是一个 private 静态方法。");
}
}
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception {
Class<?> utilsClass = Utils.class;
Method staticMethod = utilsClass.getDeclaredMethod("printStaticMessage");
staticMethod.setAccessible(true);
// 调用静态方法时,第一个参数传 null
staticMethod.invoke(null);
}
}
输出:
这是一个 private 静态方法。
总结与最佳实践
| 操作目标 | 核心API | 关键步骤 | invoke / get / set 的第一个参数 |
|---|---|---|---|
| 字段 | getDeclaredField("fieldName") |
field.setAccessible(true) |
对象实例 |
| 方法 | getDeclaredMethod("methodName", ...paramTypes) |
method.setAccessible(true) |
对象实例 |
| 构造函数 | getDeclaredConstructor(...paramTypes) |
constructor.setAccessible(true) |
不适用 (用于创建实例) |
| 静态方法 | getDeclaredMethod("staticMethodName", ...paramTypes) |
method.setAccessible(true) |
null |
安全风险与注意事项
- 破坏封装性:这是反射最大的弊端。
private的设计意图就是限制外部访问,反射直接绕过了这个限制,使得代码难以维护和理解。 - 安全性问题:如果攻击者利用反射获取或修改了敏感数据(如密码、数据库连接信息),后果不堪设想,处理敏感数据的类应格外小心。
- 性能开销:反射比直接调用方法或访问字段要慢得多,因为它需要在运行时进行额外的查找和类型检查,在性能敏感的代码中应避免使用。
- 代码可读性:反射代码通常比普通代码更晦涩难懂,增加了调试和维护的难度。
- 模块化系统 (JPMS) 的限制:在 Java 9 引入的模块化系统中,默认情况下,反射无法访问其他模块中的
private成员,需要额外配置--add-opens参数才能实现。
何时应该使用反射?
- 框架和库:Spring, Hibernate, MyBatis 等大量依赖反射来实现其核心功能,例如依赖注入、ORM 映射等。
- 单元测试:在测试时,可能需要调用
private方法来测试某个特定的逻辑分支,或者通过反射来验证对象的状态。 - 动态代理:
java.lang.reflect.Proxy使用反射来创建动态代理对象。 - 处理未知代码:在 IDE 或调试器中,需要动态地检查和修改运行时对象的内部状态。
反射是一把“双刃剑”,它能提供无与伦比的灵活性,但也带来了巨大的风险,在编写业务代码时,应尽量避免使用反射来访问 private 成员,只有在框架开发、测试等特殊场景下,才应考虑使用它。
