一、反射是什么?🤔

1. 核心定义

Java 反射(Reflection) 是指在程序运行时,动态获取类的完整结构信息(构造器、方法、字段、注解等),并能调用这些成员的机制。

  • 正常编码:编译期就固定类名、方法名,如 new Student().setId(1)
  • 反射编码:运行时才确定要操作的类和方法,如 Class.forName("Student").getMethod("setId").invoke(obj, 1)

一句话总结:反射让代码在运行时 “照镜子”,看清自身结构并动态操作,是框架底层的核心基石。

2. 核心价值

  • 动态性:无需在编译期写死类名,可通过配置 / 字符串动态加载类
  • 通用性:一套代码可操作任意类,适合开发通用工具、框架
  • 穿透性:可突破访问权限限制,操作 private 成员(需谨慎使用)

二、基础篇:反射入口 —— 获取 Class 对象

要操作类,必须先获取 Class 对象,它是反射的唯一入口。

1. 三种获取 Class 的方式

java

运行

public class GetClassDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        // 方式1:类名.class(编译期安全,最常用)
        Class<String> cls1 = String.class;

        // 方式2:对象.getClass()(必须先创建对象)
        String str = "hello";
        Class<? extends String> cls2 = str.getClass();

        // 方式3:Class.forName("全类名")(最灵活,框架底层首选)
        Class<?> cls3 = Class.forName("java.lang.String");

        // 同一个类在 JVM 中只有一个 Class 对象
        System.out.println(cls1 == cls2); // true
        System.out.println(cls2 == cls3); // true
    }
}

表格

方式 优点 缺点 适用场景
类名.class 编译期检查,无异常,性能高 需提前知道类名,不够灵活 日常开发、工具类
对象.getClass () 基于已有对象获取 必须先创建对象,无法操作未实例化类 已有对象场景
Class.forName() 最灵活,可通过字符串动态加载 需处理 ClassNotFoundException 框架底层、配置驱动

2. 面试八股

Q:为什么同一个类的 Class 对象是唯一的?A:JVM 类加载机制保证,一个类只会被加载一次,生成唯一的 Class 实例存储在方法区,因此无论通过哪种方式获取,都指向同一个对象。

Q:Class.forName () 和 类名.class 的区别?A:Class.forName() 会触发类的静态代码块执行,而 类名.class 不会触发类初始化,仅加载类信息。


三、进阶篇:解剖类的核心结构

拿到 Class 对象后,即可动态获取并操作类的三大核心成员:字段(Field)、方法(Method)、构造器(Constructor)

1. 操作字段(Field)

java

运行

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
 * 反射操作字段(Field)示例
 * 核心:1. 获取类的所有字段 2. 暴力访问private字段
 */
class Student {
    // 私有字段
    private int id;
    // 公有字段
    public String name;
}

public class FieldDemo {
    public static void main(String[] args) {
        try {
            // 1. 获取Student的Class对象
            Class<?> cls = Student.class;
            // 2. 创建Student实例(用于字段赋值/读取)
            Student s = new Student();

            // ========== 第一步:获取并打印所有字段信息 ==========
            // getDeclaredFields():获取本类所有字段(含private,不含父类)
            Field[] allFields = cls.getDeclaredFields();
            System.out.println("===== Student类所有字段信息 =====");
            for (Field f : allFields) {
                // 1. 获取字段修饰符(如private、public)
                String modifier = Modifier.toString(f.getModifiers());
                // 2. 获取字段类型(简化类名,如int、String)
                String type = f.getType().getSimpleName();
                // 3. 获取字段名
                String fieldName = f.getName();
                
                System.out.println("修饰符:" + modifier + " | 类型:" + type + " | 字段名:" + fieldName);
            }

            // ========== 第二步:暴力访问private字段 ==========
            // getDeclaredField("id"):获取指定名称的字段(含private)
            Field idField = cls.getDeclaredField("id");
            // 关键:关闭Java权限检查,允许操作private字段(暴力反射)
            idField.setAccessible(true);
            
            // 给s对象的id字段赋值100
            idField.set(s, 100);
            // 读取s对象的id字段值
            int idValue = (int) idField.get(s);
            
            System.out.println("\n===== 操作private字段结果 =====");
            System.out.println("Student对象的id值:" + idValue); // 输出100

        } catch (NoSuchFieldException e) {
            // 字段不存在时的异常处理
            System.err.println("错误:找不到指定字段");
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // 未关闭权限检查时操作private字段的异常处理
            System.err.println("错误:无权访问该字段(需调用setAccessible(true))");
            e.printStackTrace();
        } catch (Exception e) {
            // 兜底异常处理
            System.err.println("反射操作字段失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
}

2. 操作方法(Method)

java

运行

class Student {
    private int id;
    public void setId(int id) { this.id = id; }
    public int getId() { return id; }
}

public class MethodDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Student.class;
        Student s = new Student();

        // 获取所有方法(含 private)
        Method[] allMethods = cls.getDeclaredMethods();
        for (Method m : allMethods) {
            System.out.println("方法名:" + m.getName());
            System.out.println("返回值:" + m.getReturnType().getSimpleName());
            System.out.println("参数:" + Arrays.toString(m.getParameterTypes()));
        }

        // 动态调用方法
        Method setId = cls.getMethod("setId", int.class);
        setId.invoke(s, 200); // 等价于 s.setId(200)

        Method getId = cls.getMethod("getId");
        int id = (int) getId.invoke(s); // 等价于 s.getId()
        System.out.println(id); // 200
    }
}

3. 操作构造器(Constructor)

java

运行

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

public class ConstructorDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Student.class;

        // 无参构造创建对象(Java 9+ 推荐,替代过时的 Class.newInstance())
        Constructor<?> con1 = cls.getConstructor();
        Object obj1 = con1.newInstance();

        // 有参构造创建对象
        Constructor<?> con2 = cls.getConstructor(int.class, String.class);
        Object obj2 = con2.newInstance(1, "张三");
    }
}

4. 面试八股

Q:反射能访问 private 成员吗?A:可以,通过 setAccessible(true) 关闭 Java 权限检查,即可读写 private 字段、调用 private 方法,但这破坏了封装性,需谨慎使用。

Q:Class.newInstance () 为什么被废弃?A:Java 9+ 中 Class.newInstance() 被标记为过时,因为它只能调用无参构造,且无法处理构造器抛出的受检异常,推荐使用 getConstructor().newInstance()


四、实战篇:企业级案例 —— 动态对象赋值

结合实际需求,实现通过反射批量创建 Student 对象并赋值,贴近框架底层实现逻辑。

1. 需求背景

给定 idsnames 数组,利用反射创建 5 个 Student 对象,赋值后存入 Object[] 并打印。

2. 基础版实现(硬编码方法名)

java

运行

class Student {
    private int id;
    private String name;
    public void setId(int id) { this.id = id; }
    public void setName(String name) { this.name = name; }
    @Override
    public String toString() {
        return "Student{id=" + id + ", name='" + name + "'}";
    }
}

public class ReflectionBasicDemo {
    public static void main(String[] args) throws Exception {
        int[] ids = {1,2,3,4,5};
        String[] names = {"张三","李四","王五","赵六","钱七"};

        Class<?> cls = Student.class;
        Object[] objs = new Object[5];

        for (int i = 0; i < 5; i++) {
            // 创建对象
            Object obj = cls.getConstructor().newInstance();
            // 赋值 id
            Method setId = cls.getMethod("setId", int.class);
            setId.invoke(obj, ids[i]);
            // 赋值 name
            Method setName = cls.getMethod("setName", String.class);
            setName.invoke(obj, names[i]);
            // 存入数组
            objs[i] = obj;
        }

        // 打印结果
        for (Object o : objs) {
            System.out.println(o);
        }
    }
}

3. 进阶版实现(框架风格,通用匹配)

java

运行

public class ReflectionAdvancedDemo {
    public static void main(String[] args) throws Exception {
        int[] ids = {1,2,3,4,5};
        String[] names = {"张三","李四","王五","赵六","钱七"};

        Class<?> cls = Student.class;
        Object[] objs = new Object[5];
        Method[] methods = cls.getDeclaredMethods(); // 获取所有方法

        for (int i = 0; i < 5; i++) {
            Object obj = cls.getConstructor().newInstance();
            int id = ids[i];
            String name = names[i];

            // 遍历方法,自动匹配 setter 方法
            for (Method m : methods) {
                if (m.getName().startsWith("set")) { // 筛选 setter 方法
                    Class<?> paramType = m.getParameterTypes()[0];
                    // 根据参数类型自动赋值
                    if (paramType == int.class) {
                        m.invoke(obj, id);
                    } else if (paramType == String.class) {
                        m.invoke(obj, name);
                    }
                }
            }
            objs[i] = obj;
        }

        // 打印结果
        System.out.println("总共有:" + objs.length + "个学生对象");
        for (Object o : objs) {
            System.out.println(o);
        }
    }
}

4. 代码对比

表格

版本 优点 缺点 适用场景
基础版 逻辑简单,易理解 方法名硬编码,扩展性差 入门练习、简单业务
进阶版 通用性强,可适配任意类 遍历方法有轻微性能开销 框架底层、通用工具

五、高级篇:反射的应用场景与坑点

1. 核心应用场景

  • 框架底层:Spring IOC(动态创建 Bean)、MyBatis(数据库结果映射到对象)
  • 通用工具:对象拷贝(BeanUtils)、JSON 序列化(Jackson/Gson)
  • 动态代理:AOP 切面编程、事务管理
  • 配置驱动:通过 XML / 注解配置,动态加载类和方法

2. 反射的坑点(面试高频)

  • 性能损耗:反射调用比直接调用慢 10~100 倍,避免在高频循环中使用
  • 封装破坏:可访问 private 成员,违反面向对象封装原则
  • 编译期安全丢失:拼写错误(如方法名写错)只能在运行时发现
  • API 过时Class.newInstance() 已废弃,需使用 getConstructor().newInstance()
  • 安全风险setAccessible(true) 可能导致敏感数据泄露

3. 面试八股

Q:反射为什么性能差?A:1. 反射调用需要动态解析方法签名,无法享受 JIT 编译优化;2. 权限检查、参数装箱拆箱带来额外开销;3. 无法内联优化,调用栈更深。

Q:反射和动态代理的关系?A:动态代理(JDK 动态代理)底层依赖反射实现,通过反射获取目标类的方法,在运行时生成代理类并增强方法逻辑。


六、总结学习路径

  1. 入门:掌握 3 种获取 Class 的方式,理解反射核心概念
  2. 进阶:熟练使用 Field/Method/Constructor 操作类成员
  3. 实战:实现通用对象赋值工具,理解框架底层反射逻辑
  4. 深入:研究 Spring/MyBatis 源码,掌握反射在企业级框架中的应用

七、文末思考

反射是 Java 进阶的核心知识点,也是面试高频考点。它赋予了 Java 极强的动态性,但也带来了性能和安全问题。日常开发尽量少用反射,框架开发则必须精通反射—— 理解反射,才能真正读懂 Spring、MyBatis 等框架的底层逻辑。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐