反射的课堂笔记

一、反射概述

1. 核心定义

反射(Reflection)是 Java 核心机制之一,允许程序在运行时获取类的完整结构,包括属性、方法、构造器、注解等,还能动态操作类的成员,即便这些成员是私有修饰的,突破了编译期的访问限制。

2. 核心特点

动态性:运行时才确定要操作的类或对象,而非编译期硬编码,赋予程序更强的灵活性;全能性:能够访问类的所有成员,无论是公共的还是私有的;灵活性:有效降低代码耦合度,是 Spring、MyBatis 等主流框架的核心实现基础,也常用于动态代理、序列化等场景;性能损耗:相比直接调用类的成员,反射会跳过编译期优化,执行效率略低,不过可通过设置 
setAccessible(true)
 减少部分访问检查的开销。

3. 核心类(位于 
java.lang.reflect
 包)


Class
:代表类的字节码对象,是反射操作的入口,每个类在 JVM 中仅有一个对应的 
Class
 实例;
Field
:用于表示类的成员变量,可通过它读取和修改对象的成员变量值;
Method
:代表类的方法,能通过该类调用对象的对应方法;
Constructor
:表示类的构造器,用于创建类的实例对象;
Modifier
:辅助解析类成员的修饰符,比如 public、private、static 等。

二、反射的核心入口:Class 类

1. 获取 Class 对象的 3 种方式

Class 类是反射的基础,所有反射操作都围绕它展开,获取 Class 对象主要有以下三种方式:

方式 1:通过 类名.class
这种方式在编译期就能确定目标类,是最安全、高效的方式,示例如下:

Class<User> clazz1 = User.class;

方式 2:通过 对象.getClass()
需要先创建类的实例对象,再通过对象的 getClass() 方法获取,适用于运行时已知对象的场景,示例:
java
运行
User user = new User();
Class<? extends User> clazz2 = user.getClass();
方式 3:通过 Class.forName(“全类名”)
通过类的全限定名动态加载类,灵活性最高,但需要处理 ClassNotFoundException 异常,示例:
java
运行
try {
    Class<?> clazz3 = Class.forName(“com.example.reflect.User”);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
2. Class 类的常用方法
getName():获取类的全限定名,包含包名和类名;
getSimpleName():仅获取类名,不包含包名;
newInstance()(已过时):创建类的实例,要求类必须有无参构造器;
getConstructor(Class<?>…):获取类的公共构造器,参数为构造器参数类型的 Class 对象;
getDeclaredConstructor(…):获取类的所有构造器,包括私有构造器;
getMethod(String, Class<?>…):获取类的公共方法,第一个参数为方法名,后续为方法参数类型的 Class 对象;
getDeclaredMethod(…):获取类的所有方法,包括私有方法;
getField(String):获取类的公共成员变量,参数为变量名;
getDeclaredField(String):获取类的所有成员变量,包括私有变量;
getSuperclass():获取当前类父类的 Class 对象;
getInterfaces():获取当前类实现的所有接口的 Class 对象数组。
三、反射操作类的成员
准备实体类
java
运行
package com.example.reflect;

public class User {
    // 成员变量
    private Long id;
    public String name;
    protected Integer age;

    // 构造器
    public User() {}
    private User(Long id) {
        this.id = id;
    }
    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    // 方法
    public String getName() {
        return name;
    }
    private void setId(Long id) {
        this.id = id;
    }
    protected void printInfo() {
        System.out.println(“id: ” + id + “, name: ” + name);
    }
}
1. 操作构造器(Constructor)
(1)调用公共构造器
先通过 Class 对象获取对应的公共构造器,再调用 newInstance() 方法创建实例,示例:
java
运行
Class<?> userClazz = Class.forName(“com.example.reflect.User”);
// 获取无参构造器
Constructor<?> constructor1 = userClazz.getConstructor();
User user1 = (User) constructor1.newInstance();

// 获取有参构造器(参数类型为 Long 和 String)
Constructor<?> constructor2 = userClazz.getConstructor(Long.class, String.class);
User user2 = (User) constructor2.newInstance(1L, “张三”);
(2)调用私有构造器
私有构造器无法直接访问,需先调用 setAccessible(true) 取消访问检查,再创建实例,示例:
java
运行
// 获取私有构造器(参数类型为 Long)
Constructor<?> privateConstructor = userClazz.getDeclaredConstructor(Long.class);
// 取消访问限制
privateConstructor.setAccessible(true);
User user3 = (User) privateConstructor.newInstance(2L);
2. 操作成员变量(Field)
(1)访问公共变量
通过 Class 对象获取公共成员变量的 Field 对象,再调用 get() 读取值、set() 修改值,示例:
java
运行
User user = new User(1L, “张三”);
Class<?> clazz = user.getClass();

// 获取公共变量 name 对应的 Field 对象
Field nameField = clazz.getField(“name”);
// 读取变量值
String name = (String) nameField.get(user);
System.out.println(“name: ” + name); // 输出:张三

// 修改变量值
nameField.set(user, “李四”);
System.out.println(user.getName()); // 输出:李四
(2)访问私有变量
私有成员变量需先通过 setAccessible(true) 取消访问限制,再进行读写操作,示例:
java
运行
// 获取私有变量 id 对应的 Field 对象
Field idField = clazz.getDeclaredField(“id”);
// 取消访问限制
idField.setAccessible(true);

// 读取私有变量值
Long id = (Long) idField.get(user);
System.out.println(“id: ” + id); // 输出:1

// 修改私有变量值
idField.set(user, 3L);
System.out.println(idField.get(user)); // 输出:3
3. 操作方法(Method)
(1)调用公共方法
获取公共方法的 Method 对象后,调用 invoke() 方法执行,参数为调用方法的对象和方法入参,示例:
java
运行
User user = new User(1L, “张三”);
Class<?> clazz = user.getClass();

// 获取 getName() 方法(无参数)
Method getNameMethod = clazz.getMethod(“getName”);
// 调用方法
String name = (String) getNameMethod.invoke(user);
System.out.println(name); // 输出:张三
(2)调用私有方法
私有方法需先设置 setAccessible(true) 取消访问限制,再调用 invoke() 执行,示例:
java
运行
// 获取私有方法 setId(Long)
Method setIdMethod = clazz.getDeclaredMethod(“setId”, Long.class);
// 取消访问限制
setIdMethod.setAccessible(true);

// 调用私有方法
setIdMethod.invoke(user, 5L);

// 验证修改结果
Field idField = clazz.getDeclaredField(“id”);
idField.setAccessible(true);
System.out.println(idField.get(user)); // 输出:5
(3)调用带参数的方法
获取方法时需指定参数类型,调用 invoke() 时传入对应参数值,示例:
java
运行
// 假设有方法 public void setName(String name)
Method setNameMethod = clazz.getMethod(“setName”, String.class);
setNameMethod.invoke(user, “王五”);
System.out.println(user.getName()); // 输出:王五
四、反射的应用场景
1. 框架核心
Spring:IOC 容器通过反射扫描指定包下的类,创建 Bean 实例;DI 依赖注入时,通过反射为 Bean 的属性赋值;
MyBatis:将数据库查询结果集(ResultSet)通过反射映射为实体类对象,自动填充属性值;
JUnit:通过反射识别标注 @Test 注解的方法,并自动执行这些测试方法。
2. 动态代理
JDK 动态代理基于反射实现,是 AOP 切面编程的重要基础,示例:
java
运行
// 示例:JDK 动态代理
InvocationHandler handler = (proxy, method, args) -> {
    System.out.println(“方法执行前增强”);
    Object result = method.invoke(target, args); // 反射调用目标方法
    System.out.println(“方法执行后增强”);
    return result;
};
UserService proxy = (UserService) Proxy.newProxyInstance(
    UserService.class.getClassLoader(),
    new Class[]{UserService.class},
    handler
);
3. 序列化 / 反序列化
FastJSON、Jackson 等 JSON 框架,通过反射解析 Java 对象的属性,将对象转为 JSON 字符串;也能通过反射创建对象,将 JSON 字符串反序列化为 Java 对象。
4. 通用工具类
封装通用的对象拷贝工具类时,利用反射遍历源对象的所有属性,读取属性值后赋值给目标对象的对应属性,适配任意类的对象拷贝,示例:
java
运行
public static <T> T copyObject(T source, Class<T> targetClass) throws Exception {
    T target = targetClass.getConstructor().newInstance();
    Field[] fields = source.getClass().getDeclaredFields();
    for (Field field : fields) {
        field.setAccessible(true);
        Object value = field.get(source);
        Field targetField = targetClass.getDeclaredField(field.getName());
        targetField.setAccessible(true);
        targetField.set(target, value);
    }
    return target;
}
五、反射的注意事项
1. 性能问题
反射调用跳过了编译期的优化,执行速度比直接调用类成员慢。优化方式有:减少 Class.forName() 的调用次数,缓存获取到的 Class 对象;频繁调用反射方法时,使用 setAccessible(true) 取消访问检查;极端高性能场景下,可使用 ASM、CGLib 等字节码操作框架替代反射。
2. 安全限制
反射可能突破类的封装性,导致私有成员被随意篡改,违背面向对象的封装设计原则。此外,在配置了安全管理器(SecurityManager)的环境中,比如某些沙箱环境,反射的访问权限可能会被限制。
3. 代码可读性降低
反射代码偏向底层操作,逻辑相对抽象,可读性较差,调试难度也更高,非必要场景应尽量避免使用,优先选择直接调用的方式。
4. 版本兼容性
如果类的结构发生变更,比如方法名、属性名修改,基于反射的代码可能会抛出 NoSuchMethodException、NoSuchFieldException 等异常,且这些异常在编译期无法检测到,只有运行时才会暴露。
六、总结
反射是 Java 实现动态性的核心机制,其核心入口是 Class 类,所有反射操作都依赖 Class 对象展开;
反射能够访问类的所有成员,包括私有成员,但访问私有成员时需要通过 setAccessible(true) 取消访问检查;
反射主要应用于框架开发、动态代理、通用工具类、序列化 / 反序列化等场景,是各类主流框架的底层核心;
使用反射时需权衡灵活性与性能、安全性,非必要场景下优先选择直接调用类成员的方式,避免反射带来的性能损耗和代码可读性问题。

© 版权声明

相关文章

暂无评论

none
暂无评论...