Skip to content

反射概念

凌晨 2 点,你盯着屏幕上的错误日志发呆。线上 OOM 了,堆 dump 文件 2GB。运维同事扔给你一个 jar 包:「内存泄漏在这类里,自己查。」

你手里只有编译好的 class 文件,没有源码。

怎么查?

答案是:反射。Java 给你开了一扇后门,让你在运行时「照镜子」,看清类到底长什么样。

镜子里的 Java 世界

Java 是一门静态语言,代码写完就定型了。但 Class 类改变了这一点——它是 Java 对「类的描述」的描述。

不是绕口令。让我们理清关系:

class String { ... }  // 这是类
String.class          // 这是 Class 对象——类的「元信息」

每个类加载到 JVM 时,虚拟机会自动为它生成一个唯一的 Class 对象。这个对象里存储着类的所有信息:叫什么名字、属于哪个包、有哪些字段、定义了哪些方法、继承自谁、实现哪些接口。

反射,就是通过这个 Class 对象「反向」获取这些信息的过程。

反射能做什么?

不是所有事情反射都能干。来看看它的能力边界。

可以做到的

运行时「问」类要信息,这是反射的核心能力:

java
public class ReflectionCapabilities {

    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("java.util.ArrayList");

        // 问它叫什么
        System.out.println("类名: " + clazz.getName());           // java.util.ArrayList
        System.out.println("简单名: " + clazz.getSimpleName());   // ArrayList

        // 问它属于哪个包
        System.out.println("包: " + clazz.getPackage().getName()); // java.util

        // 问它继承自谁
        System.out.println("父类: " + clazz.getSuperclass().getName()); // java.util.AbstractList

        // 问它实现了哪些接口
        System.out.println("接口数量: " + clazz.getInterfaces().length);
        for (Class<?> i : clazz.getInterfaces()) {
            System.out.println("  - " + i.getSimpleName());
        }

        // 问它有哪些字段
        System.out.println("字段数量: " + clazz.getDeclaredFields().length);

        // 问它有哪些方法
        System.out.println("方法数量: " + clazz.getDeclaredMethods().length);
    }
}

运行时「操控」对象,这就把能力从「看」扩展到「改」:

java
public class ReflectionManipulation {

    static class SecretVault {
        private String password = "123456";
        private int attempts = 0;
    }

    public static void main(String[] args) throws Exception {
        SecretVault vault = new SecretVault();
        Class<?> clazz = vault.getClass();

        // 读取私有字段(正常代码根本做不到)
        Field passwordField = clazz.getDeclaredField("password");
        passwordField.setAccessible(true);  // 关键:这行代码打开了潘多拉魔盒
        String password = (String) passwordField.get(vault);
        System.out.println("密码是: " + password);

        // 修改私有字段
        passwordField.set(vault, "999999");
        System.out.println("修改后: " + passwordField.get(vault));
    }
}

不能做到的

反射不是万能的。有些事情,它做不到:

能力反射能做到吗说明
修改 final 字段的值部分可以如果 final 字段是基本类型或 String 常量,JIT 编译后值会被内联,修改无效
突破模块系统限制JDK 9+ 受限需要 --add-opens 参数显式开放模块
获取泛型具体类型不完全泛型在编译后被擦除,运行时只能看到 List,看不到 List&lt;String&gt;
访问已加载类的私有成员取决于上下文同一模块可以,跨模块需要 --add-opens

看一个反例:

java
public class GenericErasureDemo {

    static void printType(List&lt;String&gt; list) {
        // 运行时,list 的泛型信息已经丢失
        Class&lt;?&gt; clazz = list.getClass();
        System.out.println("实际类型: " + clazz.getName());
        // 只能看到: java.util.ArrayList
        // 看不到 String
    }

    public static void main(String[] args) {
        List&lt;String&gt; stringList = new ArrayList&lt;&gt;();
        List&lt;Integer&gt; intList = new ArrayList&lt;&gt;();

        printType(stringList); // 输出: java.util.ArrayList
        printType(intList);    // 输出: java.util.ArrayList
        // 两者在运行时看起来一模一样!
    }
}

核心类一览

反射的 API 主要在 java.lang.reflect 包里,核心类就这几个:

Member 接口(定义访问成员的通用行为)
├── Field    → 字段:我有什么数据
├── Method   → 方法:我能做什么
└── Constructor&lt;T&gt; → 构造:我怎么被创建

Modifier 类 → 工具类:解析修饰符(public/private/static/final...)
Array 类    → 工具类:动态操作数组
Proxy 类    → 工具类:动态代理

理解这个层次关系很重要。Field、Method、Constructor 都实现了 Member 接口,它们有共同的行为模式。

为什么需要反射?

没有反射的世界是什么样的?

不用反射:
service.saveUser(user);  // 代码里硬编码了类名

用反射:
String className = config.get("service.class");  // 从配置文件读取
Class.forName(className).newInstance().save(user);  // 动态调用

反射让 Java 拥有了「延迟绑定」的能力——程序可以推迟到运行时才决定使用哪个类、调用哪个方法。这是框架设计的基石。

Spring 为什么不需要你手动 new 一个 Bean?反射。 JUnit 怎么自动发现和执行测试方法?反射。 Jackson 怎么把 JSON 转成对象?反射。

要点回顾

  1. Class 对象是反射的入口 — 每个类在 JVM 中都有一个对应的 Class 对象
  2. 反射可以「看」也可以「改」 — 不仅能获取信息,还能修改字段值、调用方法
  3. 反射有边界 — 无法突破泛型擦除、模块系统等限制
  4. 反射是双刃剑 — 灵活的背后是性能开销和安全风险

反射不是银弹,但它是 Java 生态系统的隐形功臣。没有它,就没有现代框架的繁荣。

基于 VitePress 构建