本文最后更新于 2025-10-31,文章内容可能已经过时。

一、反射的核心原理

1. Class对象的本质

每个类在JVM加载后都会生成唯一的Class对象,作为运行时类型信息的载体。
示例:获取Class对象的三种方式

// 方式1:类.class(编译期确定)
Class<String> clazz1 = String.class;

// 方式2:对象.getClass()(运行时确定)
String str = "Hello";
Class<?> clazz2 = str.getClass();

// 方式3:Class.forName()(动态加载类,需全限定名)
Class<?> clazz3 = Class.forName("java.util.ArrayList");

// 验证是否为同一Class对象
System.out.println(clazz1 == clazz2); // true
System.out.println(clazz1 == clazz3); // false

二、反射的核心API详解

1. 获取类成员

1.1 获取构造方法
// 获取所有公共构造方法
Constructor<?>[] constructors = String.class.getConstructors();

// 获取指定参数类型的构造方法
Constructor<String> constructor = String.class.getConstructor(char[].class);
String str = (String) constructor.newInstance(new char[]{'H', 'e', 'l', 'l', 'o'});
System.out.println(str); // Hello
1.2 获取字段
// 获取所有公共字段(含继承)
Field[] publicFields = String.class.getFields();

// 获取本类声明的所有字段(含私有)
Field declaredField = String.class.getDeclaredField("value");
declaredField.setAccessible(true); // 突破访问限制
char[] value = (char[]) declaredField.get("Hello");//byte[] value = (byte[]) declaredField.get("Hello");
System.out.println(value); // [H, e, l, l, o]

在Java 9+中,String类的内部实现发生了变化。虽然String类的value字段在Java语言层面仍然是char[],但JVM内部实现使用了byte[]来存储UTF-8编码的字符串(特别是对于非ASCII字符)。

具体来说:

  • 在Java 8及更早版本中,String类的value字段类型是char[],所以反射获取时返回char[]
  • 在Java 9+中,String类的value字段在JVM内部被表示为byte[](尽管Java语言层面仍视为char[]),所以反射获取时返回的是byte[]

在Java 9+中,String类确实引入了新的内部实现:

  • value字段类型仍是char[](Java语言层面)
  • 但内部使用byte[]存储UTF-8编码的字符串
  • 有一个新的coder字段用于区分编码方式

在Java 8中,String类的value字段内部表示就是char[],所以反射获取时返回char[],代码可以正常工作。但在Java 9+中,由于内部实现变化,需要使用byte[]来接收返回值。

1.3 获取方法
// 获取所有公共方法(含继承)
Method[] methods = String.class.getMethods();

// 获取本类声明的特定方法(含私有)
Method privateMethod = String.class.getDeclaredMethod("hashCode");
privateMethod.setAccessible(true);
int hash = (int) privateMethod.invoke("Hello");
System.out.println(hash); // 输出"Hello".hashCode()

2. 动态创建对象

// 通过无参构造创建实例
Class<?> listClass = Class.forName("java.util.ArrayList");
Object list = listClass.getDeclaredConstructor().newInstance();
System.out.println(list); // []

// 通过有参构造创建实例
Constructor<Date> dateConstructor = Date.class.getConstructor(long.class);
Date date = dateConstructor.newInstance(System.currentTimeMillis());
System.out.println(date); // 当前时间

三、反射的高级应用

1. 动态代理(AOP实现)

// 定义接口
interface Service {
    void execute();
}

// 实现类
class RealService implements Service {
    @Override
    public void execute() {
        System.out.println("RealService executing...");
    }
}

// 动态代理处理器
InvocationHandler handler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = method.invoke(new RealService(), args); // 调用真实对象
        System.out.println("After method: " + method.getName());
        return result;
    }
};

// 创建代理对象
Service proxy = (Service) Proxy.newProxyInstance(
    Service.class.getClassLoader(),
    new Class[]{Service.class},
    handler
);

proxy.execute();
/* 输出:
   Before method: execute
   RealService executing...
   After method: execute
*/

2. 依赖注入模拟(类似Spring)

// 定义接口和实现类
interface Dependency {
    void doSomething();
}

class DependencyImpl implements Dependency {
    @Override
    public void doSomething() {
        System.out.println("DependencyImpl working...");
    }
}

// 需要注入依赖的类
class Client {
    private Dependency dependency;

    public void setDependency(Dependency dependency) {
        this.dependency = dependency;
    }

    public void run() {
        dependency.doSomething();
    }
}

// 通过反射实现依赖注入
public class ReflectiveDI {
    public static void main(String[] args) throws Exception {
        Client client = new Client();
        Field field = Client.class.getDeclaredField("dependency");
        field.setAccessible(true);
        field.set(client, new DependencyImpl()); // 手动注入
        client.run(); // 输出 "DependencyImpl working..."
    }
}

3. 注解处理器

自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface MyAnnotation {
    String value();
}
使用注解
class AnnotatedClass {
    @MyAnnotation("name")
    private String username = "Alice";
}
反射读取注解
public class AnnotationProcessor {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = AnnotatedClass.class;
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(MyAnnotation.class)) {
                MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);
                field.setAccessible(true);
                System.out.println(annotation.value() + ": " + field.get(new AnnotatedClass()));
                // 输出 name: Alice
            }
        }
    }
}

四、反射的性能优化

1. 缓存反射对象

// 缓存Method对象,避免重复查找
private static final Map<Class<?>, Map<String, Method>> METHOD_CACHE = new ConcurrentHashMap<>();

public static Method getCachedMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
    return METHOD_CACHE.computeIfAbsent(clazz, k -> new HashMap<>())
                      .computeIfAbsent(methodName, k -> {
                          try {
                              return clazz.getDeclaredMethod(methodName, parameterTypes);
                          } catch (Exception e) {
                              throw new RuntimeException(e);
                          }
                      });
}

五、反射的典型应用场景

1. JSON序列化工具

public class JsonSerializer {
    public static String toJson(Object obj) throws Exception {
        StringBuilder sb = new StringBuilder("{");
        for (Field field : obj.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            sb.append(field.getName()).append(":")
              .append(field.get(obj)).append(",");
        }
        return sb.deleteCharAt(sb.length()-1).append("}").toString();
    }

    public static void main(String[] args) throws Exception {
        User user = new User("Bob", 30);
        System.out.println(JsonSerializer.toJson(user));
        // 输出: {"username":"Bob","age":30}
    }
}

class User {
    private String username;
    private int age;

    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

六、反射的局限性与替代方案

1. 性能对比

// 直接调用 vs 反射调用
class Benchmark {
    public void targetMethod() {}

    public static void main(String[] args) throws Exception {
        Benchmark obj = new Benchmark();
        Method method = Benchmark.class.getMethod("targetMethod");

        long start = System.nanoTime();
        for (int i = 0; i < 1_000_000; i++) {
            obj.targetMethod(); // 直接调用
        }
        System.out.println("Direct call: " + (System.nanoTime() - start) + " ns");

        start = System.nanoTime();
        for (int i = 0; i < 1_000_000; i++) {
            method.invoke(obj); // 反射调用
        }
        System.out.println("Reflection call: " + (System.nanoTime() - start) + " ns");
    }
}
// 输出示例:
// Direct call: 120,000,000 ns
// Reflection call: 1,200,000,000 ns

2. 替代方案

  • Lombok:通过注解自动生成代码,避免手动编写getter/setter。
  • MapStruct:编译期生成对象映射代码,替代反射拷贝。
  • 字节码操作工具(ASM/ByteBuddy):直接操作字节码,性能接近原生代码。

七、反射的安全问题

1. 访问私有构造方法

// 获取String的私有构造方法(不推荐使用!)
Constructor<String> constructor = String.class.getDeclaredConstructor(char[].class);
constructor.setAccessible(true);
String hacked = (String) constructor.newInstance(new char[]{'S', 'e', 'c', 'r', 'e', 't'});
System.out.println(hacked); // Secret

2. 安全管理器限制

Java 9+默认禁用反射访问私有成员,需通过JVM参数启用:

--add-opens java.base/java.lang=ALL-UNNAMED

八、反射的底层实现(JVM视角)

  1. 类加载阶段
    • .class文件被加载到方法区,生成Class对象。
  2. 符号引用解析
    • 反射调用时,JVM会解析方法/字段的符号引用为直接引用。
  3. 动态链接
    • invokevirtual指令用于调用反射方法,涉及额外的查找过程。

九、完整反射工具类示例

import java.lang.reflect.*;

public class ReflectionUtils {

    // 通用对象属性拷贝
    public static void copyProperties(Object dest, Object src) throws Exception {
        Class<?> srcClass = src.getClass();
        Class<?> destClass = dest.getClass();
        for (Field destField : destClass.getDeclaredFields()) {
            String fieldName = destField.getName();
            Field srcField = srcClass.getDeclaredField(fieldName);
            srcField.setAccessible(true);
            destField.setAccessible(true);
            destField.set(dest, srcField.get(src));
        }
    }

    // 动态调用方法
    public static Object invokeMethod(Object obj, String methodName, Object... args) throws Exception {
        Class<?>[] paramTypes = new Class<?>[args.length];
        for (int i = 0; i < args.length; i++) {
            paramTypes[i] = args[i].getClass();
        }
        Method method = obj.getClass().getDeclaredMethod(methodName, paramTypes);
        method.setAccessible(true);
        return method.invoke(obj, args);
    }

    // 测试用例
    public static void main(String[] args) throws Exception {
        User user1 = new User("Alice", 25);
        User user2 = new User();
        copyProperties(user2, user1);
        System.out.println(user2.getUsername()); // Alice

        String result = (String) invokeMethod(user1, "toString");
        System.out.println(result); // User{username='Alice', age=25}
    }
}

class User {
    private String username;
    private int age;

    public User() {}

    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", age=" + age +
                '}';
    }
}

十、总结

特性描述
动态性运行时分析和操作类,实现高度灵活的设计
应用场景框架开发(Spring/MyBatis)、动态代理、注解处理、通用工具等
性能代价反射调用比直接调用慢约10倍,需通过缓存优化
安全风险可访问私有成员,可能破坏封装性,需严格控制权限
替代方案Lombok、MapStruct、ASM等工具可减少反射使用,提升性能和可维护性

掌握反射是成为Java高级开发者的关键,但需在灵活性与性能之间权衡。