目录

1.问题初现

2.反射登场

反射的原理

反射在这个例子中干了什么?

3.反射应该怎么用

先准备一个测试类:Person.java

用反射操作它

4.反射到底用在哪些地方?

框架的依赖注入(Spring)

单元测试框架(JUnit)

动态代理(AOP)

通用 JSON 转换工具

5.反射的优缺点

优点

 缺点

6.什么时候用反射

7.总结

前言:

反射这个词语想必大家都不陌生吧,在初学javase的时候这个点是很多人都无法理解的,这到底是什么玩意儿啊!!!,别慌今天我带大家彻底理解反射,不光从概念理解,更从实际代码出发,现在跟着我一起学习反射。

1.问题初现

你现在写了这样一段代码

// 编译时,你定义了两个动物类
class Dog {
    public void speak() { System.out.println("汪汪"); }
}
class Cat {
    public void speak() { System.out.println("喵喵"); }
}

然后你想当用户输入dog时,程序输出:”汪汪“,输入猫时,程序输出:”喵喵“,你是不是会想到直接用if----else来判断,那如果现在有100只动物呢,你会一个个去判断吗,很麻烦对吧,用循环吗,怎么循环?是不是不知道怎么办了,那么反射来拯救你。

2.反射登场

反射是 Java 提供的一种运行时自省机制,它允许程序在运行期间动态地获取类的完整信息(比如类名、方法、字段、构造器),并且可以操作这些成员——包括创建对象、调用方法、修改字段值,甚至访问私有成员。

它的核心是 Class 对象。每个类在加载到 JVM 后,都会对应一个 Class 对象,里面存着这个类的所有结构信息。反射就是通过这个 Class 对象来“窥探”和操作类的内部。

反射的原理

以上面的问题为例子,现在写一个反射代码


String animalType = 用户输入; // 运行时才能确定,比如 "Dog"
// 反射动态加载类、创建对象、调用方法
Class<?> clazz = Class.forName(animalType); // 根据字符串加载类
Object animal = clazz.newInstance();        // 创建实例 ==new animalType()
Method method = clazz.getMethod("speak");   // 获取 speak 方法
method.invoke(animal);                      // 调用方法==animal.speak()

要彻底理解反射,必须知道 Java 虚拟机(JVM)的类加载机制:

  • 当你编写一个 .java 文件,编译后得到 .class 字节码文件。

  • 当程序运行到需要用到某个类时,JVM 的类加载器会将这个类的 .class 文件读入内存,并为之创建一个 Class 对象

  • 这个 Class 对象就像该类的“元数据”代表,它包含了这个类的所有信息:类名、父类、实现的接口、构造器列表、方法列表、字段列表等。

这个class对象就是反射的入口

反射在这个例子中干了什么?

  1. 动态加载类:用 Class.forName("Dog") 把 Dog 这个类加载到内存,得到它的 Class 对象。如果没有反射,你只能用 new Dog(),但那样类名就写死了。

  2. 动态创建对象clazz.newInstance() 相当于 new Dog(),但这里事先不知道具体是哪个类。

  3. 动态查找方法getMethod("speak") 找到类里的 speak 方法。

  4. 动态调用方法invoke(animal) 实际执行 animal.speak()

整个过程都是程序运行时根据用户的输入现场决定的,而不是编译时就写死。这就是反射的价值:让程序在运行时拥有“临时改变主意”的能力,变得非常灵活.

3.反射应该怎么用

反射的套路非常固定,记住这四步:

  1. 获取 Class 对象

  2. 通过 Class 对象获取构造器、方法或字段

  3. 设置可访问(如果是私有成员,需要 setAccessible(true)

  4. 调用方法或修改字段

先准备一个测试类:Person.java

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

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

    private void setName(String name) { this.name = name; }
    public String getName() { return name; }
    public void sayHello() { System.out.println("Hello, I'm " + name); }
}

用反射操作它

import java.lang.reflect.*;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        // 1. 获取 Class 对象(三种常用方式)
        Class<?> clazz = Class.forName("Person");          // 方式1:类名字符串
        // Class<?> clazz = Person.class;                  // 方式2:类名.class
        // Person p = new Person(); Class<?> clazz = p.getClass(); // 方式3:对象.getClass()

        // 2. 创建对象(通过公有构造器)
        Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
        Object obj = constructor.newInstance("张三", 25);   // 相当于 new Person("张三", 25)
        System.out.println(obj); // 输出 Person{name='张三', age=25}

        // 3. 操作私有字段 name
        Field nameField = clazz.getDeclaredField("name");
        nameField.setAccessible(true);                // 暴力反射:允许访问私有
        nameField.set(obj, "李四");                    // 修改私有字段的值
        System.out.println("修改后 name = " + nameField.get(obj)); // 读取私有字段

        // 4. 调用私有方法 setName
        Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);
        setNameMethod.setAccessible(true);
        setNameMethod.invoke(obj, "王五");               // 相当于 obj.setName("王五")
        System.out.println("调用私有方法后 name = " + nameField.get(obj));

        // 5. 调用公有方法 sayHello
        Method sayHelloMethod = clazz.getMethod("sayHello");
        sayHelloMethod.invoke(obj);                     // 输出 Hello, I'm 王五
    }
}

看到没? 我们全程没有直接写 new Person(),也没有直接调用 obj.setName(),但通过反射,我们做到了完全相同的事情,而且类名、方法名、字段名都可以是字符串变量。

4.反射到底用在哪些地方?

框架的依赖注入(Spring)

Spring 的配置文件里写着 <bean class="com.example.UserService"/>,它怎么知道这个类在哪里?就是通过反射加载的:

Class<?> clazz = Class.forName("com.example.UserService");
Object userService = clazz.newInstance();

单元测试框架(JUnit)

JUnit 会扫描你写的测试类,找到所有加了 @Test 注解的方法,然后用反射调用它们:

for (Method method : testClass.getMethods()) {
    if (method.isAnnotationPresent(Test.class)) {
        method.invoke(testInstance);
    }
}

动态代理(AOP)

Spring AOP、MyBatis 的 Mapper 动态代理,都是通过反射 + 动态代理生成的。

通用 JSON 转换工具

String json = "{";
for (Field field : obj.getClass().getDeclaredFields()) {
    field.setAccessible(true);
    String fieldName = field.getName();
    Object value = field.get(obj);
    json += "\"" + fieldName + "\":\"" + value + "\",";
}
json = json.substring(0, json.length() - 1); // 去掉最后一个逗号
json += "}";


{"name":"张三","age":25}

obj.getClass().getDeclaredFields()

  • obj 是某个对象,比如一个 Person 实例。

  • obj.getClass() 获取这个对象的 Class 对象(就是前面说的“类的身份证”)。

  • getDeclaredFields() 返回这个类中所有声明的字段,不管它是 public 还是 private。返回的是一个 Field 数组,数组里每个元素代表一个字段。

for (Field field : 字段数组)

这就是一个增强 for 循环,依次处理每一个字段。

field.setAccessible(true);

  • 默认情况下,反射不能访问私有字段(比如 private String name)。

  • setAccessible(true) 就是“暴力打开”访问权限,告诉 Java:我知道这个字段可能是私有的,但我就是要访问它,你放行。这样后面才能读取或修改这个字段的值。

String fieldName = field.getName();

获取字段的名字,比如字段名叫 "name" 或 "age"

Object value = field.get(obj);

获取这个字段在当前对象 obj 中的实际值

  • 如果字段是 nameobj 的 name 是 "张三",那么 value 就是字符串 "张三"

  • 如果字段是 age,值是 25,那么 value 就是整数 25(注意此时 value 类型是 Object,但实际值是 Integer)。

5.反射的优缺点

优点

  • 灵活性高:可以动态加载类、调用方法,代码高度抽象。

  • 扩展性强:支持插件化架构,无需修改核心代码就能扩展功能。

 缺点

  • 性能稍慢:反射比直接调用慢几十倍(因为有类型检查、权限检查等),在循环中慎用。

  • 可能破坏封装setAccessible(true) 可以调用私有方法,如果滥用会破坏类的封装性,带来安全风险。

  • 代码复杂:反射代码往往比直接调用冗长,且需要处理一大堆受检异常(ClassNotFoundExceptionNoSuchMethodExceptionInvocationTargetException 等)。

6.什么时候用反射

你明确知道要操作哪个类,比如 new Person()不用反射,直接写
类名、方法名来自配置文件、用户输入用反射,动态处理
你在写一个通用框架或工具(如 JSON 转换、ORM)用反射,处理未知类型
你想在单元测试中调用私有方法用反射(配合 setAccessible
核心业务逻辑,对性能要求极高尽量不用反射,或使用缓存、MethodHandle 优化

反射是一把双刃剑,它让 Java 从静态语言变得“动态”起来,但也要在正确的地方使用它。希望这篇文章能帮你揭开反射的神秘面纱,下次看到反射代码时,能轻松看懂它的意图。

7.总结

反射并不神秘,它只是一种手段,能让程序员操作未知的事物,让程序变得非常灵活,如果这篇文章对你有帮助请给个赞或者关注,创作不易感谢你的阅读。

Logo

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

更多推荐