反射

84 阅读6分钟

Java反射深度解析笔记

📖 目录


基础概念

什么是反射?

Java反射是一个"透视镜",让程序在运行时能够:

  • 查看类的内部结构
  • 获取类的属性和方法
  • 创建对象并调用方法
  • 绕过访问控制限制

Class对象

每个类都有一个对应的Class对象,它是这个类的"身份证"

// 获取Class对象的三种方式
Class<?> clazz1 = String.class;                    // 方式1:类字面量
Class<?> clazz2 = "hello".getClass();              // 方式2:对象方法
Class<?> clazz3 = Class.forName("java.lang.String"); // 方式3:类名字符串

反射的基本用法

1. 创建对象

public class Student {
    private String name;
    private int age;
    
    public Student() {}
    
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

// 使用反射创建对象
Class<?> clazz = Student.class;
Student student = (Student) clazz.newInstance(); // 调用无参构造器

// 使用有参构造器
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Student student2 = (Student) constructor.newInstance("张三", 20);

2. 获取和操作属性

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Student.class;
        Student student = (Student) clazz.newInstance();
        
        // 获取私有属性
        Field nameField = clazz.getDeclaredField("name");
        nameField.setAccessible(true); // 设置可访问私有属性
        
        // 设置属性值
        nameField.set(student, "小明");
        
        // 获取属性值
        String name = (String) nameField.get(student);
        System.out.println("姓名: " + name);
    }
}

3. 调用方法

public class Student {
    private String name;
    
    public void sayHello() {
        System.out.println("Hello, I'm " + name);
    }
    
    private void study(String subject) {
        System.out.println("正在学习: " + subject);
    }
}

// 反射调用方法
Class<?> clazz = Student.class;
Student student = (Student) clazz.newInstance();

// 调用公共方法
Method sayHelloMethod = clazz.getMethod("sayHello");
sayHelloMethod.invoke(student);

// 调用私有方法
Method studyMethod = clazz.getDeclaredMethod("study", String.class);
studyMethod.setAccessible(true);
studyMethod.invoke(student, "Java");

底层原理

JVM内存结构

JVM内存布局:
┌─────────────────┐
│   方法区/元空间   │ ← 类的元数据存储在这里
├─────────────────┤
│      堆内存      │ ← 对象实例存储在这里  
├─────────────────┤
│      栈内存      │ ← 方法调用和局部变量
└─────────────────┘

类加载过程

类加载过程:
加载 → 链接(验证→准备→解析) → 初始化

方法区中存储的类信息:
┌─────────────────────────────────┐
│ Student类的元数据结构            │
├─────────────────────────────────┤
│ • 类名: com.example.Student     │
│ • 父类: java.lang.Object        │
│ • 接口列表: []                  │
│ • 访问标志: public              │
│ • 字段表:                       │
│   - name: String, private       │
│   - age: int, private           │
│ • 方法表:                       │
│   - study(): void, public       │
│   - <init>(): void, public      │
│ • 常量池                        │
│ • 属性表                        │
└─────────────────────────────────┘

Class对象内部结构

// Class对象内部结构(简化版)
public final class Class<T> {
    // 指向方法区中类元数据的指针
    private transient ClassLoader classLoader;
    private transient Class<?> componentType;
    
    // 缓存反射信息,提高性能
    private volatile transient Field[] declaredFields;
    private volatile transient Method[] declaredMethods;
    private volatile transient Constructor<T>[] declaredConstructors;
    
    // 类的元数据信息
    private transient String name;
    private transient int modifiers;
}

对象内存布局

对象内存布局:
┌─────────────────┐
│   对象头(8字节)   │ ← Mark Word + Class指针
├─────────────────┤
│   实例数据       │ ← 字段值存储区域
│   name: "张三"   │   ← String引用(8字节)
│   age: 20       │   ← int值(4字节)
├─────────────────┤
│   对齐填充       │ ← 保证8字节对齐
└─────────────────┘

底层Native实现

// JVM中的C++代码(简化版)
// 获取字段值
oop Reflection::field_get(jfieldID field_id, oop obj) {
    // 1. 获取字段偏移量
    int offset = field_id->offset();
    
    // 2. 根据字段类型读取内存
    BasicType field_type = field_id->field_type();
    
    switch (field_type) {
        case T_OBJECT:
            return obj->obj_field(offset);  // 读取对象引用
        case T_INT:
            return java_lang_Integer::create(obj->int_field(offset));
        case T_LONG:
            return java_lang_Long::create(obj->long_field(offset));
    }
}

反射访问完整过程

// 完整的反射访问过程
public class ReflectionProcess {
    public static void main(String[] args) throws Exception {
        Student student = new Student();
        
        // 1. 获取Class对象 - 从对象头中获取类型指针
        Class<?> clazz = student.getClass();
        
        // 2. 查找字段 - 遍历类元数据中的字段表
        Field nameField = clazz.getDeclaredField("name");
        
        // 3. 设置可访问 - 修改AccessibleObject的override标志
        nameField.setAccessible(true);
        
        // 4. 设置值 - 计算字段偏移量,直接写入内存
        nameField.set(student, "小明");
        
        // 5. 获取值 - 根据偏移量从内存读取
        String name = (String) nameField.get(student);
    }
}

性能优化机制

1. 缓存机制

// Class对象中的缓存
private volatile transient Field[] declaredFields;

// 第一次调用时创建,后续直接返回缓存
public Field[] getDeclaredFields() throws SecurityException {
    if (declaredFields == null) {
        declaredFields = getDeclaredFields0(false);
    }
    return declaredFields.clone();
}

2. 方法访问器优化

// 反射调用优化:从解释执行到编译执行
// 前15次调用使用native方法
// 第16次开始生成字节码,直接调用

Method method = clazz.getMethod("toString");
for (int i = 0; i < 20; i++) {
    method.invoke(obj); // 第16次后性能显著提升
}

3. Unsafe类:最接近底层的操作

import sun.misc.Unsafe;

public class UnsafeExample {
    private static Unsafe unsafe;
    
    public static void directMemoryAccess() {
        Student student = new Student("张三", 20);
        
        try {
            // 获取字段的偏移量
            Field nameField = Student.class.getDeclaredField("name");
            long offset = unsafe.objectFieldOffset(nameField);
            
            // 直接从内存读取字段值
            Object value = unsafe.getObject(student, offset);
            System.out.println("通过Unsafe读取: " + value);
            
            // 直接向内存写入字段值
            unsafe.putObject(student, offset, "李四");
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

实际应用场景

1. 配置文件驱动

// 从配置文件读取类名,动态创建对象
String className = "com.example.UserService";
Class<?> clazz = Class.forName(className);
Object service = clazz.newInstance();

2. 框架开发(类似Spring)

// 框架扫描注解,自动创建对象
@Service
public class UserService {
    // ...
}

// 框架代码
if (clazz.isAnnotationPresent(Service.class)) {
    Object bean = clazz.newInstance();
    // 注册到容器中
}

3. 通用工具方法

public class ObjectUtils {
    // 通用的对象复制方法
    public static void copyProperties(Object source, Object target) {
        Class<?> sourceClass = source.getClass();
        Class<?> targetClass = target.getClass();
        
        Field[] fields = sourceClass.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                Object value = field.get(source);
                
                Field targetField = targetClass.getDeclaredField(field.getName());
                targetField.setAccessible(true);
                targetField.set(target, value);
            } catch (Exception e) {
                // 处理异常
            }
        }
    }
}

4. 类分析工具

public class ClassAnalyzer {
    public static void analyzeClass(Class<?> clazz) {
        System.out.println("=== 分析类: " + clazz.getName() + " ===");
        
        // 获取所有字段
        Field[] fields = clazz.getDeclaredFields();
        System.out.println("字段信息:");
        for (Field field : fields) {
            System.out.printf("  %s %s %s%n", 
                Modifier.toString(field.getModifiers()),
                field.getType().getSimpleName(),
                field.getName());
        }
        
        // 获取所有方法
        Method[] methods = clazz.getDeclaredMethods();
        System.out.println("方法信息:");
        for (Method method : methods) {
            System.out.printf("  %s %s %s(%s)%n",
                Modifier.toString(method.getModifiers()),
                method.getReturnType().getSimpleName(),
                method.getName(),
                Arrays.toString(method.getParameterTypes()));
        }
    }
}

注意事项

优点

  • 灵活性:运行时动态操作类和对象
  • 通用性:编写通用的框架和工具
  • 扩展性:支持插件化架构
  • 框架支持:Spring、Hibernate等框架的核心技术

缺点

  • 性能开销:比直接调用慢10-100倍
  • 安全风险:可以访问私有成员,破坏封装性
  • 代码复杂:增加维护难度,降低可读性
  • 编译时检查缺失:错误只能在运行时发现

最佳实践

  1. 缓存反射对象
// 好的做法:缓存Method对象
private static final Method TO_STRING_METHOD;
static {
    try {
        TO_STRING_METHOD = Object.class.getMethod("toString");
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    }
}
  1. 异常处理
try {
    Method method = clazz.getMethod("methodName");
    method.invoke(obj);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
    // 具体的异常处理
    logger.error("反射调用失败", e);
}
  1. 性能考虑
// 在性能敏感的地方避免使用反射
// 或者使用MethodHandle等更高效的替代方案
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findVirtual(String.class, "length", MethodType.methodType(int.class));

核心要点总结

  1. 反射本质:JVM提供的运行时类信息访问能力
  2. 实现原理:基于JVM内存中的类元数据和Native方法
  3. 性能特点:有缓存优化,但仍比直接调用慢
  4. 应用场景:框架开发、配置驱动、通用工具
  5. 使用原则:适度使用,注意性能和安全性

记住:反射是Java的高级特性,虽然强大但要谨慎使用。理解其原理有助于更好地使用和优化反射代码!