网站Logo 苏叶的belog

反射

wdadwa
9
2025-12-20

一,什么是反射?

反射(Reflection) 是 Java 在运行时检视自身的能力。它允许程序在运行时:

  • 获取类的完整信息
  • 构造对象
  • 调用方法
  • 访问/修改字段
  • 甚至绕过访问权限控制
// 没有反射:一切都必须在编译时确定
User user = new User();  // 编译时就知道是User类
user.setName("张三");     // 编译时就知道有setName方法

// 有反射:运行时才决定
Class<?> clazz = Class.forName("com.example.User");  // 运行时才知道是哪个类
Object obj = clazz.newInstance();                    // 运行时创建对象
Method method = clazz.getMethod("setName", String.class);  // 运行时找方法
method.invoke(obj, "张三");                           // 运行时调用

二,获取class对象

2953321-20251220203804063-1251093309.png

获取 Class 对象的三种方式

// 方式1:类名.class(最常用,编译时检查)
Class<User> clazz1 = User.class;

// 方式2:对象.getClass()
User user = new User();
Class<? extends User> clazz2 = user.getClass();

// 方式3:Class.forName()(最灵活,运行时加载)
Class<?> clazz3 = Class.forName("com.example.User");

三,获取类中信息

利用 class 类获取类中的信息方法:

  1. 通过Constructorfieldmethod判断这个方法是获取构造方法,成员变量,成员方法。
  2. 如果加上了Declared代表获取所有的类型的东西
  3. 最后末尾加上s代表获取所有
  4. 一般格式为get+Delcared(可以不加,不加代表只获得公共的)+Constructor/field/method+s(这个s可以不加代表获取指定的)

反射 API 命名规律速查表

API格式含义示例
getXxx获取公共的XxxgetMethod()
getDeclaredXxx获取所有的Xxx(包括私有)getDeclaredMethod()
getXxxs获取所有公共的XxxgetMethods()
getDeclaredXxxs获取所有的XxxgetDeclaredMethods()

3.1 反射获取构造方法

2953321-20240607223309535-1912518678.png

代码举例:

Student 类

package domain;

public class Student {
    private int age;
    private String name;

    public Student() {
        System.out.println("无参构造器执行了~")
    }

    public Student(String name, int age) {
        System.out.println("有参构造器执行了~")
        this.name = name;
        this.age = age;
    }
}

测试类:

    @Test
    public void testRelfct() throws NoSuchMethodException {
        Class<Student> studentClass = Student.class;

        //1.获取全部public修饰的构造器
        Constructor<?>[] constructors1 = studentClass.getConstructors();
        //2.获取全部的构造器
        Constructor<?>[] constructors2 = studentClass.getDeclaredConstructors();

        //3.获取public修饰的指定构造器
        //获取指定public修饰的无参构造
        Constructor<Student> constructor1 = studentClass.getConstructor();
        //获取指定public修饰的有参构造
        Constructor<Student> constructor2 = studentClass.getConstructor(String.class,int.class);
        //获取指定无参构造
        Constructor<Student> declaredConstructor1 = studentClass.getDeclaredConstructor();
        //获取指定有参构造
        Constructor<Student> declaredConstructor2 = studentClass.getDeclaredConstructor(String.class,int.class);
    }

获取类构造器的作用:依然是初始化对象返回

2953321-20240607224825260-1659180482.png

    @Test
    public void testRelfct() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<Student> studentClass = Student.class;

        //获取指定无参构造(反射一般不会填写数据类型,为了确保代码的通用性)
        Constructor<Student> declaredConstructor1 = studentClass.getDeclaredConstructor();
        //获取指定有参构造(反射一般不会填写数据类型,为了确保代码的通用性)
        Constructor<Student> declaredConstructor2 = studentClass.getDeclaredConstructor(String.class,int.class);
        //调用构造器创建对象(如果构造器是private修饰的就必须设置为可访问)
        declaredConstructor1.setAccessible(true);
        Student student = declaredConstructor1.newInstance();
        Student student1 = declaredConstructor2.newInstance("张三", 18);
    }

3.2 反射获取字段(成员变量)

2953321-20240607225351414-398529632.png

代码举例:

Student 类

package domain;

public class Student {
    public static int a;
    public static final String COUNTRY="中国";
    private int age;
    private String name;

    public Student() {
    }

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

测试类:

    @Test
    public void testRelfct() throws NoSuchFieldException {
        Class<Student> studentClass = Student.class;
        //1.获取全部public修饰的成员变量
        Field[] fields = studentClass.getFields();
        //2.获取全部成员变量
        Field[] declaredFields = studentClass.getDeclaredFields();
        //3.获取指定public修饰的成员变量
        Field name = studentClass.getField("name");
        //4.获取指定成员变量
        Field age = studentClass.getDeclaredField("age");
    }

获取到成员变量的作用:依然是赋值、取值。

2953321-20240607230213069-1811793688.png

    @Test
    public void testRelfct() throws NoSuchFieldException, IllegalAccessException {
        Class<Student> studentClass = Student.class;

        Field name = studentClass.getDeclaredField("name");
        Student s=new Student("张三",13);
        System.out.println(s.getName()+":"+s.getAge());
        name.setAccessible(true);//禁止访问权限检查,进行暴力反射
        name.set(s,"李四");//将对象s的name属性设置为"李四"
        System.out.println(s.getName()+":"+s.getAge());
        System.out.println(name.get(s));//获取对象s的name属性的值
    }

3.3 反射获取成员方法

2953321-20240607230850746-1861068295.png

代码举例:

Student 类

package domain;

public class Student {
    public static int a;
    public static final String COUNTRY="中国";
    private int age;
    private String name;

    public Student() {
    }

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

    public void eat(){
        System.out.println("吃");
    }
    public void eat(String name){
        System.out.println(name+"吃");
    }
    private void move(String name){
        System.out.println(name+"移动");
    }
}

测试类:

    @Test
    public void testRelfct() throws NoSuchMethodException {
        Class<Student> studentClass = Student.class;

        //1.获取所有public修饰的方法
        Method[] methods = studentClass.getMethods();
        //2.获取所有方法
        Method[] declaredMethods = studentClass.getDeclaredMethods();
        //3.获取指定方法
        Method eat1 = studentClass.getMethod("eat");//获取public修饰的无参eat方法
        Method eat2 = studentClass.getMethod("eat", String.class);//获取public修饰的有参eat方法
        //获取有参eat方法
        Method eat3 = studentClass.getDeclaredMethod("eat");//获取public修饰的无参eat方法
        Method eat4 = studentClass.getDeclaredMethod("eat", String.class);
        
    }

成员方法的作用:依旧是执行

2953321-20240607231328023-709809052.png

    @Test
    public void testRelfct() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<Student> studentClass = Student.class;

        Method move = studentClass.getDeclaredMethod("move",String.class);

        Student s=new Student("张三",13);
        move.setAccessible(true);//设置访问权限,暴力反射
        move.invoke(s,"小猫");//调用对象s的move方法
    }

四,反射的作用

反射让程序在运行时能「看到」自己的结构,并「操作」这些结构。

就像游戏里的「上帝模式」:能看到所有信息,能修改所有东西。

4.1 动态探查

// 运行时才知道这是什么类,还能查看它的"内部构造"
public void inspectClass(Object obj) {
    Class<?> clazz = obj.getClass();
    
    // 看类的基本信息
    System.out.println("类名: " + clazz.getName());
    System.out.println("父类: " + clazz.getSuperclass().getName());
    
    // 看类的"器官"(字段)
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        System.out.println("字段: " + field.getName() + " - " + field.getType());
    }
    
    // 看类的"技能"(方法)
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
        System.out.println("方法: " + method.getName());
    }
    
    // 看类的"身份证"(注解)
    Annotation[] annotations = clazz.getAnnotations();
    for (Annotation anno : annotations) {
        System.out.println("注解: " + anno.annotationType().getName());
    }
}

实际应用

  • IDE的代码提示功能
  • 调试工具查看对象状态
  • 生成API文档

4.2 动态创建

// 工厂不知道具体要造什么,运行时才知道
public Object createInstance(String className) throws Exception {
    // 1. 加载类(根据名字找图纸)
    Class<?> clazz = Class.forName(className);
    
    // 2. 创建实例(按图纸造人)
    return clazz.newInstance();
}

// 使用:传入什么类名就创建什么对象
Object user = createInstance("com.example.User");
Object product = createInstance("com.example.Product");
Object service = createInstance("com.example.Service");

实际应用

  • Spring IoC容器(@Autowired
  • 插件系统(动态加载jar包)
  • 配置文件驱动

4.3 动态调用

// 调用未知对象的方法
public Object invokeMethod(Object obj, String methodName, Object... args) 
        throws Exception {
    Class<?> clazz = obj.getClass();
    
    // 1. 找到方法(按名字找技能)
    Class<?>[] paramTypes = getParameterTypes(args);
    Method method = clazz.getDeclaredMethod(methodName, paramTypes);
    
    // 2. 调用方法(执行技能)
    method.setAccessible(true);  // 私有方法也能调用
    return method.invoke(obj, args);
}

// 使用
User user = new User();
invokeMethod(user, "setName", "张三");
invokeMethod(user, "setAge", 25);
String name = (String) invokeMethod(user, "getName");

实际应用

  • AOP切面编程(Spring AOP)
  • 远程方法调用(RPC)
  • 测试框架(JUnit调用测试方法)

4.4 动态修改

// 修改对象的内部状态
public void modifyObject(Object obj, Map<String, Object> values) 
        throws Exception {
    Class<?> clazz = obj.getClass();
    
    for (Map.Entry<String, Object> entry : values.entrySet()) {
        String fieldName = entry.getKey();
        Object value = entry.getValue();
        
        // 找到字段(找到要整的部位)
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);  // 突破private限制
        
        // 修改值(动手术)
        field.set(obj, value);
    }
}

// 使用:把私有字段name改成"李四"
User user = new User();
Map<String, Object> changes = new HashMap<>();
changes.put("name", "李四");
modifyObject(user, changes);  // 即使name是private也能改

实际应用

  • ORM框架设置主键
  • 反序列化(JSON转对象)
  • Mock测试(修改final字段)

五,反射的性能损失

5.1 性能对比数据

public class ReflectionPerformance {
    public static void main(String[] args) throws Exception {
        User user = new User("张三", 25);
        
        // 1. 直接调用(基准)
        long start = System.nanoTime();
        for (int i = 0; i < 1000000; i++) {
            String name = user.getName();
        }
        long directTime = System.nanoTime() - start;
        
        // 2. 反射调用(不缓存)
        Method getNameMethod = User.class.getMethod("getName");
        start = System.nanoTime();
        for (int i = 0; i < 1000000; i++) {
            String name = (String) getNameMethod.invoke(user);
        }
        long reflectTime = System.nanoTime() - start;
        
        // 3. 反射调用(缓存Method)
        // 上面的getNameMethod已经缓存
        
        // 4. 反射调用(缓存 + setAccessible)
        getNameMethod.setAccessible(true);  // 关闭访问检查
        start = System.nanoTime();
        for (int i = 0; i < 1000000; i++) {
            String name = (String) getNameMethod.invoke(user);
        }
        long reflectAccessibleTime = System.nanoTime() - start;
        
        System.out.println("=== 性能对比(100万次调用)===");
        System.out.println("直接调用:       " + directTime / 1000000 + " ms");
        System.out.println("反射调用(无缓存): " + reflectTime / 1000000 + " ms");
        System.out.println("反射调用(缓存):   " + reflectAccessibleTime / 1000000 + " ms");
        System.out.println("\n性能比例:");
        System.out.println("反射(无缓存) / 直接 = " + reflectTime / directTime + " 倍");
        System.out.println("反射(缓存) / 直接 = " + reflectAccessibleTime / directTime + " 倍");
    }
}

// 典型输出结果:
// === 性能对比(100万次调用)===
// 直接调用:       5 ms
// 反射调用(无缓存): 320 ms
// 反射调用(缓存):   65 ms
//
// 性能比例:
// 反射(无缓存) / 直接 = 64 倍
// 反射(缓存) / 直接 = 13 倍

结论:最差情况慢60 倍,优化后仍慢10 倍以上!

5.2 反射为什么慢?

反射之所以慢是八大链路导致的

  1. 安全检查(Security Check)

    // 每次反射调用都要做这些检查:
    Method method = clazz.getMethod("getName");
    
    // invoke()内部要检查:
    // 1. 权限检查(Can I access this method?)
    if (!method.isAccessible()) {
        // 检查调用者是否有权限访问
        SecurityManager.checkPermission(new ReflectPermission("suppressAccessChecks"));
    }
    
    // 2. 参数类型检查(Are the arguments correct?)
    if (!method.getParameterTypes()[0].isInstance(args[0])) {
        throw new IllegalArgumentException("Argument type mismatch");
    }
    
    // 3. 返回类型检查(Is the return type correct?)
    // 4. 异常检查(Does it throw the right exceptions?)
    
    // 直接调用时,这些检查在编译时完成,反射在运行时每次都要做!
    
  2. 方法查找(Method Lookup)

    // getMethod()的内部操作
    public Method getMethod(String name, Class<?>... parameterTypes) {
        // 1. 安全检查(每次都要检查权限)
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        
        // 2. 在方法表中线性搜索(复杂度O(n))
        Method method = getMethod0(name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(name);
        }
        
        // 3. 返回方法的副本(不是缓存的原件!)
        return getReflectionFactory().copyMethod(method);
    }
    
    // 直接调用时:方法地址在编译时就确定了
    // 0x1234: invokevirtual #5  // Method User.getName:()Ljava/lang/String;
    // 直接跳转到方法地址,无需查找
    
  3. 参数装箱 / 拆箱(Boxing/Unboxing)

    public class BoxingCost {
        public void test(int num) {}  // 原始类型
        
        public static void main(String[] args) throws Exception {
            Method method = BoxingCost.class.getMethod("test", int.class);
            BoxingCost obj = new BoxingCost();
            
            // 反射调用:需要把int装箱为Integer
            Object[] params = {100};  // 自动装箱:int → Integer
            
            long start = System.nanoTime();
            for (int i = 0; i < 1000000; i++) {
                method.invoke(obj, params);  // 内部要拆箱:Integer → int
            }
            System.out.println("反射调用: " + (System.nanoTime() - start));
            
            // 直接调用:直接传int
            start = System.nanoTime();
            for (int i = 0; i < 1000000; i++) {
                obj.test(100);  // 直接传递原始类型
            }
            System.out.println("直接调用: " + (System.nanoTime() - start));
        }
    }
    // 输出:反射调用慢更多,因为多了装箱拆箱开销
    
  4. 可变参数处理(Varargs)

    // invoke()方法签名:Object invoke(Object obj, Object... args)
    // 这意味着:
    // 1. 参数要包装成Object数组
    // 2. 数组要分配内存(如果不在栈上分配)
    // 3. 参数要复制到数组中
    
    // 直接调用:参数直接在栈上传递
    obj.method("arg1", 2, 3.0);
    
    // 反射调用:参数要装箱成数组
    Object[] args = {"arg1", Integer.valueOf(2), Double.valueOf(3.0)};
    method.invoke(obj, args);
    
  5. 异常包装(Exception Wrapping)

    try {
        method.invoke(obj, args);
    } catch (InvocationTargetException e) {
        // 反射把原始异常包装了一层
        Throwable cause = e.getCause();
        throw cause;
    }
    
    // 直接调用的异常:
    try {
        obj.method();
    } catch (SpecificException e) {
        // 直接抛出,没有包装
    }
    
    // 反射的异常处理链:
    // 1. 调用方法
    // 2. 如果抛异常,捕获并包装为InvocationTargetException
    // 3. 重新抛出
    // 多了一层包装和栈追踪
    
  6. 无法内联优化(No Inlining)

 ```java
 // JIT编译器优化示例
 public class InliningDemo {
     public int add(int a, int b) {
         return a + b;  // 简单方法
     }
     
     public static void main(String[] args) {
         InliningDemo demo = new InliningDemo();
         
         // 直接调用:JIT可以内联优化
         // 编译后可能变成:result = a + b; (没有方法调用开销)
         int result = 0;
         for (int i = 0; i < 1000000; i++) {
             result += demo.add(i, i+1);  // 可能被内联
         }
         
         // 反射调用:无法内联
         Method addMethod = InliningDemo.class.getMethod("add", int.class, int.class);
         for (int i = 0; i < 1000000; i++) {
             result += (int) addMethod.invoke(demo, i, i+1);  // 必须走完整的方法调用
         }
     }
 }
 ```

7. 内存分配(Memory Allocation)

 ```java
 // 反射调用涉及的对象创建
 public class MemoryAllocation {
     public static void main(String[] args) throws Exception {
         Method method = MemoryAllocation.class.getMethod("test");
         MemoryAllocation obj = new MemoryAllocation();
         
         // 每次invoke()可能创建:
         // 1. Object[] 参数数组(即使参数为空)
         // 2. 包装异常对象
         // 3. 方法访问器对象
         
         // HotSpot JVM使用动态生成的MethodAccessor
         // 第一次调用:生成NativeMethodAccessorImpl
         // 第15次调用:生成GeneratedMethodAccessor1(字节码)
         // 这个生成过程本身就很耗时!
     }
 }
 ```

8. 调用链更长(Longer Call Chain)

 ```java
 // 直接调用的调用栈
 User.getName()
   ↓
 返回结果
 
 // 反射调用的调用栈
 Method.invoke()
   ↓
 NativeMethodAccessorImpl.invoke()
   ↓
 GeneratedMethodAccessor1.invoke()
   ↓
 DelegatingMethodAccessorImpl.invoke()
   ↓
 User.getName()  // 最终调用实际方法
   ↓
 返回,层层返回
 
 // 多了4层调用栈!
 ```

六,总结

反射的本质:让程序在运行时不再「盲人摸象」,而是有了「透视眼」和「操控杆」,可以查看和操作自己的内部结构。

反射的价值:实现「动态性」—— 在编译时不知道,运行时才知道要干什么。

反射的代价:性能损失、安全性降低、代码复杂。

最后建议反射是框架开发者的瑞士军刀,普通开发者的核武器—— 知道怎么用,但别轻易用。

动物装饰