反射(Reflection)是 Java 语言的核心特性之一,它允许程序在运行时获取类的信息、操作类的属性和方法,甚至创建对象。这种动态访问能力使反射成为框架开发、注解处理等场景的基础,但同时也带来了复杂性和性能问题。本文将系统讲解反射的使用方法与关键细则。
一、反射的核心概念与作用
反射机制打破了 Java 的封装性,允许程序在运行时执行以下操作:
- 获取类的完整结构(类名、父类、接口、字段、方法等)
- 动态创建类的实例
- 动态调用类的方法(包括私有方法)
- 动态修改类的字段值(包括私有字段)
核心作用:实现动态编程,使代码能够适配未知的类结构(如 Spring 的 IOC 容器、MyBatis 的 ORM 映射)。
二、反射的基础使用步骤
反射的核心操作围绕java.lang.Class类展开,它是反射的入口点。使用反射通常分为 3 个步骤:
2.1 获取 Class 对象(反射入口)
获取Class对象有 3 种常用方式:
注意:
- 每个类的
Class对象是唯一的(单例),上述 3 种方式获取的是同一个对象 Class.forName()需要处理ClassNotFoundException- 基本类型(如
int)和数组也有对应的Class对象
2.2 获取类的成员(字段、方法、构造器)
通过Class对象可以获取类的各种成员:
2.2.1 获取字段(Field)
Class<?> clazz = User.class;
// 获取public字段(包括父类)
Field publicField = clazz.getField("username");
// 获取所有public字段
Field[] publicFields = clazz.getFields();
// 获取当前类的指定字段(包括private)
Field privateField = clazz.getDeclaredField("password");
// 获取当前类的所有字段(包括private)
Field[] allFields = clazz.getDeclaredFields();
2.2.2 获取方法(Method)
Class<?> clazz = User.class;
// 获取public方法(包括父类)
Method publicMethod = clazz.getMethod("getName");
// 获取带参数的public方法
Method loginMethod = clazz.getMethod("login", String.class, String.class);
// 获取当前类的指定方法(包括private)
Method privateMethod = clazz.getDeclaredMethod("encrypt", String.class);
// 获取当前类的所有方法(包括private)
Method[] allMethods = clazz.getDeclaredMethods();
2.2.3 获取构造器(Constructor)
Class<?> clazz = User.class;
// 获取public构造器
Constructor<?> publicConstructor = clazz.getConstructor(String.class, int.class);
// 获取当前类的指定构造器(包括private)
Constructor<?> privateConstructor = clazz.getDeclaredConstructor(int.class);
// 获取当前类的所有构造器
Constructor<?>[] allConstructors = clazz.getDeclaredConstructors();
2.3 操作类的成员(核心功能)
获取成员后,可以进行创建对象、调用方法、修改字段等操作:
2.3.1 创建对象
Class<?> clazz = User.class;
// 方式1:通过无参构造器(需public)
User user1 = (User) clazz.newInstance();
// 方式2:通过指定构造器(可访问private)
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // 突破private限制
User user2 = (User) constructor.newInstance("zhangsan", 20);
2.3.2 调用方法
User user = new User("lisi", 25);
Class<?> clazz = user.getClass();
// 调用public方法
Method setAgeMethod = clazz.getMethod("setAge", int.class);
setAgeMethod.invoke(user, 26); // 等价于 user.setAge(26)
// 调用private方法
Method privateMethod = clazz.getDeclaredMethod("toString");
privateMethod.setAccessible(true); // 关键:开启访问权限
String result = (String) privateMethod.invoke(user);
2.3.3 修改字段值
User user = new User("wangwu", 30);
Class<?> clazz = user.getClass();
// 修改public字段
Field ageField = clazz.getField("age");
ageField.set(user, 31); // 等价于 user.age = 31
// 修改private字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 关键:开启访问权限
nameField.set(user, "zhaoliu"); // 等价于 user.name = "zhaoliu"
三、反射使用的关键细则
3.1 访问权限控制(setAccessible 的使用)
Java 的访问控制(private/protected/public)会限制反射操作,需通过setAccessible(true)突破限制:
// 对私有成员必须开启访问权限
privateField.setAccessible(true);
privateMethod.setAccessible(true);
privateConstructor.setAccessible(true);
注意:
setAccessible(true)仅能绕过语言层面的访问控制,不能突破 JVM 的安全管理器(SecurityManager)限制- 滥用会破坏封装性,增加代码风险
3.2 异常处理
反射操作会抛出多种受检异常,必须显式处理:
try {
Class<?> clazz = Class.forName("com.example.User");
Method method = clazz.getMethod("test");
method.invoke(clazz.newInstance());
} catch (ClassNotFoundException e) {
// 类不存在
} catch (NoSuchMethodException e) {
// 方法不存在
} catch (IllegalAccessException e) {
// 访问权限不足(未设置setAccessible(true))
} catch (InstantiationException e) {
// 实例化失败(如抽象类、接口)
} catch (InvocationTargetException e) {
// 方法内部抛出异常(e.getCause()获取实际异常)
}
3.3 性能问题与优化
反射操作比直接调用慢 10-100 倍,主要原因是:
- 类型检查和安全验证
- 动态解析而非编译期确定
优化策略:
- 缓存反射对象(
Class、Method、Field等),避免重复获取// 缓存Method对象 private static final Map<String, Method> methodCache = new HashMap<>(); - 减少反射调用次数,核心逻辑尽量用直接调用
- 使用
MethodHandle(JDK 7+)替代反射,性能更优
3.4 泛型擦除问题
反射无法直接获取泛型的实际类型(因泛型擦除):
// 无法直接获取List<String>中的String类型
Field listField = clazz.getDeclaredField("stringList");
Type type = listField.getGenericType();
if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
Type[] actualTypes = pType.getActualTypeArguments();
// actualTypes[0] 即为String的Class对象
}
3.5 数组操作的特殊性
数组的反射操作需要使用java.lang.reflect.Array工具类:
// 创建数组
int[] intArray = (int[]) Array.newInstance(int.class, 5);
// 设置数组元素
Array.set(intArray, 0, 10);
// 获取数组元素
int value = (int) Array.get(intArray, 0);
四、反射的典型应用场景
4.1 框架开发(IOC/DI)
Spring 的 IOC 容器通过反射创建对象并注入依赖:
// 简化版IOC逻辑
public Object getBean(String className) throws Exception {
Class<?> clazz = Class.forName(className);
Object obj = clazz.newInstance();
// 注入依赖(通过反射设置字段)
return obj;
}
4.2 注解处理
自定义注解通常通过反射解析:
// 解析类上的注解
Class<?> clazz = User.class;
if (clazz.isAnnotationPresent(Entity.class)) {
Entity entity = clazz.getAnnotation(Entity.class);
String tableName = entity.value(); // 获取注解属性
}
4.3 序列化与反序列化
JSON 框架(如 Jackson)通过反射将对象转为 JSON:
// 简化版序列化逻辑
public String toJson(Object obj) throws Exception {
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
StringBuilder json = new StringBuilder("{");
for (Field field : fields) {
field.setAccessible(true);
json.append("\"").append(field.getName()).append("\":")
.append("\"").append(field.get(obj)).append("\",");
}
json.append("}");
return json.toString();
}
4.4 单元测试
测试框架(如 JUnit)通过反射调用 @Test 注解的方法:
// 简化版测试执行逻辑
public void runTests(Class<?> testClass) throws Exception {
Object testObj = testClass.newInstance();
for (Method method : testClass.getMethods()) {
if (method.isAnnotationPresent(Test.class)) {
method.invoke(testObj); // 执行测试方法
}
}
}
五、反射使用的注意事项
- 安全性问题:反射可能绕过访问控制,修改私有成员,破坏类的封装设计
- 版本兼容性:依赖具体类结构的反射代码,在类升级(如字段重命名)时易出错
- 可读性下降:反射代码比直接调用更难理解和调试
- 无法优化:JVM 难以对反射操作进行优化,可能影响性能
- 谨慎使用场景:
- 普通业务代码应避免使用反射
- 优先考虑接口、继承等 OO 方式解决问题
- 必须使用时,尽量限制在框架底层或工具类中
六、总结
反射是 Java 中一把 "双刃剑":它赋予程序强大的动态能力,是众多框架的基础,但也带来了性能损耗、安全性问题和代码复杂性。
使用反射的核心原则:
- 能不用则不用,优先选择编译期确定的代码
- 必须使用时,做好缓存和权限控制
- 充分测试,考虑类结构变化的兼容性
理解反射的工作原理和使用细则,不仅能更好地运用框架,还能在必要时编写灵活的动态代码,这是 Java 开发者从初级迈向高级的重要一步。
3638

被折叠的 条评论
为什么被折叠?



