一、什么是反射(Reflection)
反射 是 Java 在运行时 动态获取类的信息(如类名、方法、字段、构造器等),并操作类对象的能力。简单说:在程序运行期间,你甚至可以动态了解一个未知的类,并调用它的方法。
类比理解
假设你有一个密封的黑盒子(类),正常情况下你只能通过盒子上的按钮(公有方法)操作它。而反射就像给了你一把螺丝刀,允许你拆开盒子,直接查看内部结构(私有方法/字段),甚至修改它。
二、反射的核心类
反射的核心 API 在 java.lang.reflect
包中:
Class
:代表一个类或接口Field
:代表类的成员变量(字段)Method
:代表类的方法Constructor
:代表类的构造方法
三、反射的基本使用步骤
1. 获取 Class 对象
要操作一个类,首先要获取它的 Class
对象。有 3 种方式:
java
// 方式1:通过 类名.class
Class<?> clazz = String.class;
// 方式2:通过 对象.getClass()
String str = "Hello";
Class<?> clazz = str.getClass();
// 方式3:通过 全类名加载(最常用!)
Class<?> clazz = Class.forName("java.lang.String");
2. 创建对象实例
通过 Class
对象创建实例:
java
// 使用默认无参构造器
Object obj = clazz.newInstance(); // 已过时,推荐用下面的方式
Constructor<?> constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
// 使用带参构造器(例如:String 的构造器)
Constructor<?> constructor = clazz.getConstructor(String.class);
Object obj = constructor.newInstance("Hello Reflection!");
3. 调用方法
动态调用对象的方法:
java
Method method = clazz.getMethod("方法名", 参数类型.class);
Object result = method.invoke(obj, 参数值);
// 示例:调用 String 的 substring 方法
String str = "Hello";
Method substringMethod = String.class.getMethod("substring", int.class);
String result = (String) substringMethod.invoke(str, 2);
System.out.println(result); // 输出 "llo"
4. 访问字段
获取或修改对象的字段(包括私有字段):
java
// 获取公有字段
Field field = clazz.getField("字段名");
// 获取私有字段(需设置可访问)
Field privateField = clazz.getDeclaredField("privateField");
privateField.setAccessible(true); // 突破私有限制
// 读取字段值
Object value = privateField.get(obj);
// 修改字段值
privateField.set(obj, "New Value");
四、反射的典型应用场景
- 框架开发:如 Spring 通过反射创建 Bean、实现依赖注入。
- 动态代理:如 AOP 中的方法拦截。
- 注解处理:运行时读取注解信息。
- 通用工具:如 JSON 序列化库(Jackson/Gson)通过反射将对象转为 JSON。
五、反射的优缺点
✅ 优点
- 灵活性:动态操作类,适合编写通用框架。
- 突破封装:可以访问私有成员(慎用!)。
❌ 缺点
- 性能低:反射操作比直接调用慢(但现代 JVM 优化后差距缩小)。
- 安全隐患:可能破坏类的封装性。
- 代码可读性差:反射代码通常难以维护。
六、完整示例
假设有一个 Student
类:
java
public class Student {
private String name;
private void sayHello() {
System.out.println("Hello, my name is " + name);
}
}
使用反射操作:
java
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
// 1. 获取 Class 对象
Class<?> clazz = Class.forName("Student");
// 2. 创建实例(使用默认构造器)
Object student = clazz.getDeclaredConstructor().newInstance();
// 3. 访问私有字段并赋值
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(student, "Alice");
// 4. 调用私有方法
Method sayHelloMethod = clazz.getDeclaredMethod("sayHello");
sayHelloMethod.setAccessible(true);
sayHelloMethod.invoke(student); // 输出:Hello, my name is Alice
}
}
七、注意事项
- 性能敏感场景慎用:反射操作比直接调用慢。
- 安全问题:反射可以绕过访问控制,需谨慎处理。
- 优先使用正常调用:反射是"最后的手段",不要为了炫技而滥用。
这些已经学会了,我们继续深入探讨 Java 反射的高级特性和底层机制,结合性能优化 、设计模式 和实际开发场景,彻底掌握反射的精髓。
一、反射的底层机制
1. 反射与 JVM 的关系
Java 反射的底层由 JVM 的 "方法句柄"(Method Handles) 和 "动态字节码生成" 支撑:
sun.reflect
包 (JDK 内部实现,慎用!)中的ReflectionFactory
类可直接生成方法访问器- 示例:直接生成方法访问器(破坏性操作!)
java
Method sayHelloMethod = Student.class.getDeclaredMethod("sayHello");
// 绕过安全检查,直接生成高效访问器
MethodAccessor accessor = ReflectionFactory.getReflectionFactory()
.newMethodAccessor(sayHelloMethod);
accessor.invoke(student, null); // 性能接近直接调用
2. 反射如何突破 final 限制
反射甚至可以修改 final
字段的值(但极不推荐!):
java
public class Config {
public static final String KEY = "DEFAULT";
}
// 修改 final 字段
Field field = Config.class.getField("KEY");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, "HACKED_VALUE"); // 修改成功!
二、反射的进阶应用
1. 动态代理(Dynamic Proxy)
反射实现动态接口代理(如 Spring AOP 的基石):
java
public class DebugProxy implements InvocationHandler {
private Object target;
public DebugProxy(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After method: " + method.getName());
return result;
}
// 创建代理对象
public static <T> T createProxy(T target, Class<T> interfaceClass) {
return (T) Proxy.newProxyInstance( // 用于在运行时动态生成实现了指定接口的代理对象
target.getClass().getClassLoader(), // 类加载器
new Class<?>[] { interfaceClass }, // 要代理的接口数组
new DebugProxy(target) // 方法调用处理器
);
}
}
// 使用示例
List<String> list = DebugProxy.createProxy(new ArrayList<>(), List.class);
list.add("test"); // 会触发代理逻辑
2. 注解的运行时处理
反射结合注解实现动态逻辑(如 JUnit 测试框架):
java
@Retention(RetentionPolicy.RUNTIME)
@interface TestCase {
String expected();
}
class CalculatorTest {
@TestCase(expected = "4")
public String testAdd() {
return String.valueOf(2 + 2);
}
}
// 注解处理器
public static void runTests(Class<?> testClass) throws Exception {
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(TestCase.class)) {
TestCase annotation = method.getAnnotation(TestCase.class);
String result = (String) method.invoke(testClass.getDeclaredConstructor().newInstance());
if (!annotation.expected().equals(result)) {
throw new AssertionError("Test failed: " + method.getName());
}
}
}
}
三、性能优化技巧
1. 缓存反射对象
反射对象(Method/Field)的获取成本较高,应缓存重用:
java
public class ReflectCache {
private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();
public static Method getMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
String key = clazz.getName() + "#" + name;
return METHOD_CACHE.computeIfAbsent(key, k -> {
try {
Method method = clazz.getMethod(name, paramTypes);
method.setAccessible(true);
return method;
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
}
2. 使用 MethodHandle(Java 7+)
比传统反射更接近 JVM 层面的高效调用:
java
public class MethodHandleDemo {
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType type = MethodType.methodType(String.class, int.class, int.class);
MethodHandle handle = lookup.findVirtual(String.class, "substring", type);
String str = "Hello World";
String result = (String) handle.invokeExact(str, 6, 11);
System.out.println(result); // 输出 "World"
}
}
四、反射与模块化(Java 9+)
在模块化系统中,反射访问非导出包需要显式授权:
java
module com.example.myapp {
requires java.base;
opens com.example.internal to spring.core; // 允许反射访问私有成员
exports com.example.api; // 公开访问的包
}
五、黑科技:利用 Unsafe 突破反射限制
sun.misc.Unsafe
提供更底层的内存操作(慎用!):
java
public class UnsafeDemo {
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
// 直接分配内存创建对象(绕过构造器)
Student student = (Student) unsafe.allocateInstance(Student.class);
System.out.println(student.getName()); // 输出 null(未初始化)
}
}
六、反射的替代方案
技术 | 特点 | 适用场景 |
---|---|---|
反射 API | 标准库支持,功能全面 | 通用动态操作 |
MethodHandle | JVM 层面优化,性能更高 | 高频调用场景 |
ASM/CGLIB | 字节码操作,完全控制类行为 | AOP、性能敏感框架 |
LambdaMetafactory | 动态生成 Lambda 表达式 | 函数式接口动态实现 |
七、实战:手写简易 IOC 容器
java
public class SimpleIOC {
private Map<String, Object> beans = new HashMap<>();
public SimpleIOC(String packageName) throws Exception {
// 扫描包路径下的类
Reflections reflections = new Reflections(packageName);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(Component.class);
// 实例化并注入依赖
for (Class<?> clazz : types) {
Object instance = clazz.getDeclaredConstructor().newInstance();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
Object dependency = beans.get(field.getType().getName());
field.setAccessible(true);
field.set(instance, dependency);
}
}
beans.put(clazz.getName(), instance);
}
}
public <T> T getBean(Class<T> type) {
return (T) beans.get(type.getName());
}
}
// 使用示例
@Component
class UserService {
@Autowired
private OrderService orderService;
// ...
}
八、关键总结
- 反射的阴暗面:可以突破几乎所有 Java 语言限制,但会破坏封装性
- 性能不是绝对瓶颈:合理缓存 + MethodHandle 可接近直接调用性能
- 现代框架基石:Spring、Hibernate、Jackson 等均重度依赖反射
- 安全大于灵活 :生产环境慎用
setAccessible(true)
和 Unsafe 操作
要更深入了解底层,推荐阅读《深入理解Java虚拟机》第8章和第9章!