0%

反射

Java 反射机制详解:动态操作类的终极武器

反射(Reflection)是 Java 语言的核心特性之一,它允许程序在运行时动态获取类的信息(如属性、方法、构造器等),并能动态调用类的方法、修改属性值。这一机制为框架开发(如 Spring、MyBatis)提供了强大的灵活性,也是理解 Java 动态性的关键。本文将系统讲解反射的操作流程、核心 API 及应用场景。

反射的核心作用

在编译期,Java 代码需要明确知道类的结构才能进行操作(如 new 实例调用方法)。而反射打破了这一限制,允许程序在运行时:

  • 动态获取类的元数据(类名、父类、接口、注解等);
  • 动态创建类的实例;
  • 动态调用类的方法(包括私有方法);
  • 动态修改类的属性(包括私有属性)。

这使得程序可以应对未知的类结构,极大地提升了代码的灵活性和扩展性。

获取 Class 对象:反射的入口

反射操作的第一步是获取目标类的 Class 对象(类的字节码对象),它是访问类元数据的入口。Java 提供了 4 种获取 Class 对象的方式:

方式一:通过类名 .class 获取

直接通过类名调用 class 属性,适用于编译期已知类名的场景:

1
2
// 获取 Person 类的 Class 对象
Class<Person> clazz = Person.class;
  • 特点:无需创建实例,效率高,编译时检查类是否存在。

方式二:通过实例 .getClass() 获取

通过对象实例的 getClass() 方法获取,适用于已有实例的场景:

1
2
Person person = new Person();
Class<?> clazz = person.getClass(); // 返回 person 所属类的 Class 对象
  • 特点:需要先创建实例,适用于动态获取实例的类型(如多态场景)。

方式三:通过 Class.forName() 获取

通过类的全限定名(包名 + 类名)动态加载类,适用于编译期未知类名的场景(如配置文件中指定类名):

1
2
String className = "com.example.Person"; // 类的全限定名
Class<?> clazz = Class.forName(className);
  • 特点:支持动态加载,类名可来自配置文件或用户输入,若类不存在会抛出 ClassNotFoundException

方式四:通过类加载器 loadClass() 获取

通过类加载器(ClassLoader)的 loadClass() 方法加载类,适用于自定义类加载场景:

1
2
3
ClassLoader classLoader = Test.class.getClassLoader(); // 获取当前类的类加载器
String className = "com.example.Person";
Class<?> clazz = classLoader.loadClass(className);
  • 特点:与 Class.forName() 类似,但不会执行类的静态代码块(Class.forName() 会触发静态代码块执行)。

反射操作核心 API

获取 Class 对象后,可通过其提供的方法获取类的详细信息(构造器、属性、方法等),并进行动态操作。

获取构造器(Constructor)

构造器用于创建类的实例,反射可获取所有构造器(包括私有)并动态创建对象。

方法 作用 范围
getConstructors() 获取所有公共构造器 本类 + 父类(仅公共)
getConstructor(Class<?>...) 获取指定参数的公共构造器 本类
getDeclaredConstructors() 获取所有构造器(包括私有、保护) 本类
getDeclaredConstructor(Class<?>...) 获取指定参数的任意构造器 本类

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
Class<?> clazz = Person.class;

// 1. 获取所有公共构造器
Constructor<?>[] publicConstructors = clazz.getConstructors();
for (Constructor<?> c : publicConstructors) {
System.out.println("公共构造器:" + c);
}

// 2. 获取指定参数的私有构造器(如 Person(String name))
Constructor<?> privateConstructor = clazz.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true); // 破除访问权限限制(关键!)
Person person = (Person) privateConstructor.newInstance("张三"); // 动态创建实例

获取属性(Field)

属性包括成员变量,反射可获取所有属性(包括私有)并动态修改其值。

方法 作用 范围
getFields() 获取所有公共属性 本类 + 父类(仅公共)
getField(String name) 获取指定名称的公共属性 本类 + 父类(仅公共)
getDeclaredFields() 获取所有属性(包括私有、保护) 本类
getDeclaredField(String name) 获取指定名称的任意属性 本类

示例代码

1
2
3
4
5
6
7
8
9
10
11
Class<?> clazz = Person.class;
Person person = new Person();

// 1. 获取私有属性 "age" 并修改值
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // 允许访问私有属性
ageField.set(person, 20); // 为 person 实例的 age 属性赋值 20

// 2. 获取公共属性 "name" 并取值
Field nameField = clazz.getField("name"); // 假设 name 是 public
String name = (String) nameField.get(person); // 获取属性值

获取方法(Method)

方法是类的行为,反射可获取所有方法(包括私有)并动态调用。

方法 作用 范围
getMethods() 获取所有公共方法 本类 + 父类(仅公共)
getMethod(String name, Class<?>...) 获取指定名称和参数的公共方法 本类 + 父类(仅公共)
getDeclaredMethods() 获取所有方法(包括私有、保护) 本类
getDeclaredMethod(String name, Class<?>...) 获取指定名称和参数的任意方法 本类

示例代码

1
2
3
4
5
6
7
8
9
10
11
Class<?> clazz = Person.class;
Person person = new Person();

// 1. 调用公共方法 "setName(String)"
Method setNameMethod = clazz.getMethod("setName", String.class);
setNameMethod.invoke(person, "李四"); // 等价于 person.setName("李四")

// 2. 调用私有方法 "privateMethod()"
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true); // 允许访问私有方法
privateMethod.invoke(person); // 等价于 person.privateMethod()

获取其他元数据

除构造器、属性、方法外,Class 对象还可获取类的其他信息:

方法 作用
getName() 获取类的全限定名(如 com.example.Person
getSimpleName() 获取类的简单名(如 Person
getSuperclass() 获取父类的 Class 对象
getInterfaces() 获取实现的所有接口的 Class 对象数组
getAnnotations() 获取类上的所有注解
isInterface() 判断是否为接口
isEnum() 判断是否为枚举类

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Class<?> clazz = Person.class;

// 获取父类
Class<?> superClass = clazz.getSuperclass();
System.out.println("父类:" + superClass.getSimpleName());

// 获取实现的接口
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> iface : interfaces) {
System.out.println("实现的接口:" + iface.getSimpleName());
}

// 获取类上的注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation anno : annotations) {
System.out.println("类注解:" + anno);
}

反射的核心操作:动态创建与调用

反射的核心价值在于动态操作类,以下是两个典型场景:

动态创建实例

通过反射调用构造器创建实例,无需在编译期知道类名:

1
2
3
4
5
6
7
8
9
10
// 从配置文件读取类名(模拟框架场景)
String className = "com.example.Person";
Class<?> clazz = Class.forName(className);

// 调用无参构造器(要求类必须有无参构造器)
Object instance = clazz.newInstance();

// 调用有参构造器
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
Object instance2 = constructor.newInstance("张三", 20);

动态调用方法

通过反射调用方法,支持任意参数和访问权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Class<?> clazz = Person.class;
Object instance = clazz.newInstance();

// 动态调用方法:methodName 为方法名,params 为参数列表
String methodName = "setName";
Object[] params = {"王五"};

// 获取方法(参数类型通过 params 推断)
Class<?>[] paramTypes = new Class[params.length];
for (int i = 0; i < params.length; i++) {
paramTypes[i] = params[i].getClass();
}
Method method = clazz.getMethod(methodName, paramTypes);

// 调用方法
method.invoke(instance, params); // 等价于 instance.setName("王五")

反射的优缺点与注意事项

优点:

  • 灵活性高:可动态操作未知类,适合框架开发(如 Spring 的 IOC 容器通过反射创建对象);
  • 功能强大:可访问私有成员,突破类的访问权限限制。

缺点:

  • 性能损耗:反射操作绕过编译期检查,需要动态解析类信息,性能比直接调用低(约慢 10~100 倍);
  • 安全性风险:可访问私有成员,可能破坏类的封装性;
  • 代码可读性差:反射代码较繁琐,不如直接调用直观。

注意事项:

  1. setAccessible(true) 的使用
    访问私有成员(构造器、属性、方法)时,必须调用 setAccessible(true) 关闭访问检查,否则会抛出 IllegalAccessException
  2. 性能优化
    频繁使用的反射操作可通过缓存 ConstructorMethodField 对象提升性能(这些对象线程安全)。
  3. 异常处理
    反射操作可能抛出多种受检异常(如 ClassNotFoundExceptionNoSuchMethodException),需妥善处理。

反射的典型应用场景

  1. 框架开发
    • Spring IOC 容器:通过反射创建 Bean 实例,依赖注入时动态设置属性;
    • MyBatis:通过反射将数据库查询结果映射为 Java 对象。
  2. 动态代理
    JDK 动态代理基于反射实现,通过 InvocationHandler 动态调用目标方法(如 AOP 切面增强)。
  3. 注解处理
    自定义注解(如 @Controller@RequestMapping)需通过反射获取注解信息并执行相应逻辑。
  4. 序列化与反序列化
    如 JSON 框架(Jackson、Gson)通过反射将 JSON 字符串转换为 Java 对象。

欢迎关注我的其它发布渠道

表情 | 预览
Powered By Valine
v1.3.10