通俗易懂学习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章!

相关推荐
计算机毕业设计小帅12 小时前
【2026计算机毕业设计】基于Springboot的校园电动车短租平台
spring boot·后端·课程设计
调试人生的显微镜12 小时前
Web前端开发工具实战指南 从开发到调试的完整提效方案
后端
静心观复12 小时前
drawio画java的uml的类图时,class和interface的区别是什么
java·uml·draw.io
Java水解12 小时前
【SQL】MySQL中空值处理COALESCE函数
后端·mysql
Laplaces Demon12 小时前
Spring 源码学习(十四)—— HandlerMethodArgumentResolver
java·开发语言·学习
guygg8812 小时前
Java 无锁方式实现高性能线程
java·开发语言
ss27312 小时前
手写Spring第7弹:Spring IoC容器深度解析:XML配置的完整指南
java·前端·数据库
Python私教12 小时前
DRF:Django REST Framework框架介绍
后端·python·django
间彧12 小时前
Java HashMap如何合理指定初始容量
后端
用户40993225021212 小时前
PostgreSQL全表扫描慢到崩溃?建索引+改查询+更统计信息三招能破?
后端·ai编程·trae