通俗易懂学习Java反射

一、什么是反射(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");

四、反射的典型应用场景

  1. 框架开发:如 Spring 通过反射创建 Bean、实现依赖注入。
  2. 动态代理:如 AOP 中的方法拦截。
  3. 注解处理:运行时读取注解信息。
  4. 通用工具:如 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
    }
}

七、注意事项

  1. 性能敏感场景慎用:反射操作比直接调用慢。
  2. 安全问题:反射可以绕过访问控制,需谨慎处理。
  3. 优先使用正常调用:反射是"最后的手段",不要为了炫技而滥用。

这些已经学会了,我们继续深入探讨 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;
    // ...
}

八、关键总结

  1. 反射的阴暗面:可以突破几乎所有 Java 语言限制,但会破坏封装性
  2. 性能不是绝对瓶颈:合理缓存 + MethodHandle 可接近直接调用性能
  3. 现代框架基石:Spring、Hibernate、Jackson 等均重度依赖反射
  4. 安全大于灵活 :生产环境慎用 setAccessible(true) 和 Unsafe 操作

要更深入了解底层,推荐阅读《深入理解Java虚拟机》第8章和第9章!

相关推荐
Asthenia041219 分钟前
Spring事务分析:@Transactional用久了,是不是忘了编程式事务了?
后端
王网aaa27 分钟前
堆结构和堆排序
java·算法·排序算法
无名指的等待7121 小时前
SpringBoot实现一个Redis限流注解
spring boot·redis·后端
张志翔的博客1 小时前
RK3588 openssl-3.4.1 编译安装
开发语言·后端·scala
计算机-秋大田1 小时前
基于Spring Boot的小区疫情购物系统的设计与实现(LW+源码+讲解)
java·vue.js·spring boot·后端·课程设计
loveking61 小时前
SpringBoot调用华为云短信实现发短信功能
java·spring boot·华为云
李长渊哦2 小时前
Spring Boot 约定大于配置:实现自定义配置
java·spring boot·后端
Asthenia04122 小时前
分析 Full GC 如何排查:详细步骤指南
后端
上官美丽2 小时前
单一责任原则在Java设计模式中的深度解析
java·开发语言·设计模式
橙序研工坊2 小时前
Java基础语法练习42(基本绘图-基本的事件处理机制-小坦克的绘制-键盘控制坦克移动)
java·开发语言