在这里插入图片描述


参考文章:

JVM内存模型
Maven从入门到精通

前言

Java反射机制赋予了语言强大的元编程能力,让程序能够“自我认知”和动态调整。这在现代软件开发中不可或缺,尤其是在追求松耦合和可配置性的场景下。尤其建议初学者学习反射,可以:

  • 学习类加载机制
  • 学习“元”编程思想
  • 便于学习Java主流开发框架原理

一、什么是反射?

反射是指Java程序在运行时能够检查、访问和修改类、接口、字段、方法和构造函数的信息。简单说,就是让你的代码能“看清”自己或别的类的内部结构,甚至动态地调用方法或改变属性。

  • 核心包:反射主要通过java.lang.reflect包实现,比如Class、Field、Method、Constructor等类。
  • 为什么叫“反射”? 因为它像一面镜子,让程序能“反射”出自己的样子。

在Java中,一切都是对象,包括类本身。每个类在加载时都会有一个对应的Class对象,这个对象就是反射的入口。

简而言之:只要你给我一个类我就能获取这个类的所有信息。

二、什么是Class类

Class 类的官方定义如下:

Class 是 Java 反射机制的核心类,位于 java.lang 包中。它的实例表示正在运行的 Java 应用程序中的类(class)、接口(interface)、枚举(enum)、数组(array)、基本类型(如 int、void) 等类型的 运行时元数据。

更直白地说:

  • 每个类型在 JVM 中都有且仅有一个对应的 Class 对象。
  • 这个对象由 JVM 自动创建,不是通过 new 生成的。
  • 它保存了该类型的所有结构信息:名称、字段、方法、构造器、修饰符、父类、实现的接口、注解等。

三、一个类是怎么被加载的

在这里插入图片描述
前提:JVM内存模型
Maven从入门到精通
要真正理解 Java 反射,必须搞清楚 类加载 和 反射机制 之间的关系。

JVM 先通过类加载器把 .class 文件变成内存中的 Class 对象,然后你的代码通过这个 Class 对象去动态获取类的结构并操作实例,也就是反射。


从 .java 到堆中对象,本质是:源码 → 字节码 → 类元数据(方法区)→ Class 对象(堆)→ 实例对象(堆),让我带你一步步剖析

1.编译(Compile)
执行命令:mvn compile ->调用 javac 编译所有 .java 文件 -> 输出 .class 文件到 target/classes/


2.加载(Loading)—— JVM 启动后首次使用类时

图中类加载器加载部分:

  1. 读取类的字节码文件也就是class文件
  2. 解析 为 JVM 内部数据结构
  3. 在方法区分配内存,存储:类名,方法,字段等类的基本信息
  4. 在堆中创建 java.lang.Class 对象
    → 这个对象是“类的运行时表示”,内部持有一个指向方法区元数据的 native 指针

3.链接(LInking)
分为三步:

① 验证(Verification)

  • 检查字节码是否合法

② 准备(Preparation)

  • 为 static 变量分配内存
  • 设置 默认初始值
    public static int x = 100; // 此时 x = 0,不是 100!
    

③ 解析(Resolution,可选延迟)

  • 将符号引用(如 "java/lang/Object")转为直接引用

4.初始化(Initialization)

  • 执行 <clinit> 方法:包括所有 static 变量的显式赋值 和 static {} 块
    public static int x = 100; // ← 现在 x = 100
    static { System.out.println("Initializing..."); }
    
  • 父类先于子类初始化

至此,类才算加载完成,可以开始使用反射了,接下来讲讲如何使用。


四、 如何使用Java反射

Java反射的使用主要通过java.lang.reflect包下的类来实现。下面以代码示例说明基本操作。

  1. 获取Class对象:
    有三种常见方式:
    • 通过类名:Class<?> clazz = Class.forName("java.lang.String");
    • 通过对象实例:Class<?> clazz = "hello".getClass();
    • 通过类字面量:Class<?> clazz = String.class;

有了Class对象你就可以开始挖掘,类的信息了,如类名、修饰符、方法、字段、接口、注解等。

  1. 访问字段:

    import java.lang.reflect.Field;
    
    public class ReflectionExample {
        private String name = "Java";
    
        public static void main(String[] args) throws Exception {
            Class<?> clazz = ReflectionExample.class;
            Field field = clazz.getDeclaredField("name"); // 获取私有字段
            field.setAccessible(true); // 绕过访问控制
            ReflectionExample instance = new ReflectionExample();
            field.set(instance, "Reflection"); // 修改字段值
            System.out.println(instance.name); // 输出: Reflection
        }
    }
    

    这里,我们通过getDeclaredField获取私有字段,并使用setAccessible(true)忽略访问修饰符。

  2. 调用方法:

    import java.lang.reflect.Method;
    
    public class ReflectionExample {
        public void greet(String message) {
            System.out.println("Hello, " + message);
        }
    
        public static void main(String[] args) throws Exception {
            Class<?> clazz = ReflectionExample.class;
            Object instance = clazz.getDeclaredConstructor().newInstance(); // 创建实例
            Method method = clazz.getMethod("greet", String.class); // 获取方法
            method.invoke(instance, "World"); // 调用方法,输出: Hello, World
        }
    }
    

另外, invoke方法允许动态执行任何方法,支持参数传递。

需要注意的是,反射操作可能会抛出异常,如NoSuchMethodException或IllegalAccessException,因此实际代码中应使用try-catch块。


五、举个栗子

你是一个程序员你想实现一个简单的 Web 服务,支持两个接口:

// 接口1: /user/123 → 返回用户
//接口2: /order/456 → 返回订单

没有反射时,你只能这样写主逻辑:

public class WebServer {
    public void handleRequest(String path) {
        if (path.startsWith("/user/")) {
            String id = extractId(path);
            UserController uc = new UserController();
            User user = uc.getUser(id);               // ← 硬编码调用
            sendJsonResponse(user);
        }
        else if (path.startsWith("/order/")) {
            String id = extractId(path);
            OrderController oc = new OrderController();
            Order order = oc.getOrder(id);             // ← 又一处硬编码
            sendJsonResponse(order);
        }
        else {
            send404();
        }
    }
}
  • 每加一个 Controller,就要改 handleRequest 方法;

有了反射 + 注解

你只需要写:

@RestController
public class UserController {
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable String id) { ... }
}

@RestController
public class OrderController {
    @GetMapping("/order/{id}")
    public Order getOrder(@PathVariable String id) { ... }
}

而 Spring 启动时自动做这件事:

// 扫描所有带 @RestController 的类
for (Class<?> clazz : allClassesWithRestController) {
    Object instance = clazz.getDeclaredConstructor().newInstance(); // 反射创建对象
    
    for (Method method : clazz.getMethods()) {
        if (method.isAnnotationPresent(GetMapping.class)) {
            String path = method.getAnnotation(GetMapping.class).value()[0];
            
            // 把路径和“方法调用逻辑”存起来
            routeMap.put(path, (request) -> {
                String id = extractIdFromPath(request.path);
                return method.invoke(instance, id); // ← 反射调用!
            });
        }
    }
}

// 处理请求时:
public void handleRequest(String path) {
    Handler handler = routeMap.get(matchedPath);
    Object result = handler.handle(request); // 自动调用对应方法
    sendJsonResponse(result);
}

这就是反射的强大之处:它让程序能在运行时动态地“看见”并“操控”任意类的结构和行为,即使这些类在编写代码时根本不存在。

结语:反射的功能很强大,赋予开发者动态处理类的能力,在框架和插件领域功不可没,但是使用时需要考虑性能,安全,可读性等问题

Logo

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

更多推荐