跳到内容

元数据卡

  • 前置知识:第13章前半(注解)、第7章(面向对象基础)
  • 预计时间:35 分钟
  • 完成标志:能通过 Class 对象获取类的字段和方法;能动态调用方法;理解注解 + 反射的组合原理

标签贴上了。然后谁来读它们? 如果有人拿一把剑走进工坊,剑柄上刻着字,你得有一个工具来认字。Java 里读标签的工具叫反射——程序在运行时查看自己的结构。

第三幕:Class 对象——类的档案袋

JVM 加载一个类时,会在堆里创建唯一的 Class<?> 对象。获取它有三种方式:

java
// Java 5+
// javac ClassDemo.java && java ClassDemo
public class ClassDemo {
    public static void main(String[] args) throws Exception {
        Class<?> c1 = "hello".getClass();              // 通过对象
        Class<?> c2 = String.class;                    // 类字面量
        Class<?> c3 = Class.forName("java.lang.String"); // 全限定名

        System.out.println(c1 == c2);  // true
        System.out.println(c2 == c3);  // true
        System.out.println(c1.getSimpleName());  // String
        System.out.println(c1.getSuperclass());  // class java.lang.Object
    }
}

预期输出:

text
true
true
String
class java.lang.Object

Class.forName() 最强大——类名可从配置文件读取,写代码时不需要知道它。

查看结构——方法、字段、构造器

拿到档案袋后,翻翻里面有什么:

java
// Java 5+
// javac InspectDemo.java && java InspectDemo
import java.lang.reflect.*;
class Person { private String name; public int age;
    public String getName() { return name; }
    private void secret() {}
}
public class InspectDemo {
    public static void main(String[] args) {
        Class<?> clazz = Person.class;
        System.out.println("公有方法:");
        for (Method m : clazz.getMethods())
            System.out.println("  " + m.getName());
        System.out.println("本类字段(含私有):");
        for (Field f : clazz.getDeclaredFields())
            System.out.println("  " + f.getType().getSimpleName() + " " + f.getName());
    }
}

预期输出(简化):

text
公有方法:
  getName  wait  equals  toString  getClass  notify
本类字段(含私有):
  String name
  int age

关键区别:getMethods() 拿到所有 public 方法(含继承),getDeclaredMethods() 拿到本类所有方法(含私有,不含继承)。字段同理。

第四幕:动态调用——运行时决定一切

你写代码时不知道类名和方法名,运行时再确定:

java
// Java 5+
// javac DynamicDemo.java && java DynamicDemo
import java.lang.reflect.*;
public class DynamicDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("java.util.ArrayList");
        Object list = clazz.getConstructor().newInstance();

        Method add = clazz.getMethod("add", Object.class);
        add.invoke(list, "第一条");
        add.invoke(list, "第二条");

        Method size = clazz.getMethod("size");
        System.out.println("大小: " + size.invoke(list)); // 2
    }
}

预期输出:大小: 2

私有方法也能调——但不该随便用

java
// javac PrivateDemo.java && java PrivateDemo
// 需要上面定义的 Person 类
Method m = p.getClass().getDeclaredMethod("secret");
m.setAccessible(true);  // 绕过 private
m.invoke(p);  // "私有方法"

预期输出:私有方法

setAccessible(true) 是反射的越权开关。框架用它注入字段,你的业务代码别碰。

差异窗口:Python 的反射更直接——getattr(obj, 'name')'name' in obj.__dict__ 开箱即用。C++ 没有内置反射,不能运行时枚举字段和方法。

第五幕:注解 + 反射 = 框架的基石

之前的 @Loggable 现在配合反射来使用:

java
// javac LoggableProcessor.java && java LoggableProcessor
import java.lang.reflect.Method;
public class LoggableProcessor {
    public static void process(Object obj, String name, Object... args)
            throws Exception {
        Class<?> clazz = obj.getClass();
        for (Method m : clazz.getDeclaredMethods()) {
            if (!m.getName().equals(name)) continue;
            if (!m.isAnnotationPresent(Loggable.class)) continue;
            Loggable log = m.getAnnotation(Loggable.class);
            System.out.println("[" + log.level() + "] 执行 " + name);
            m.invoke(obj, args);
            System.out.println("[" + log.level() + "] 完成");
            return;
        }
    }
    public static void main(String[] args) throws Exception {
        process(new OrderService(), "placeOrder", "u1", "i1");
    }
}

预期输出:

text
[DEBUG] 执行 placeOrder
下单中...
[DEBUG] 完成

这就是 AOP 的雏形:只贴标签,处理器自动拦截。Spring 的 @Transactional@GetMapping 背后都是同样的原理。

性能警告

反射比直接调用慢 1-2 个数量级。业务代码不要用反射,框架才用。高频调用要缓存 Method 对象,不要循环里反复查。

验收标准

  • 获取 Class 对象的三种方式是什么?
  • getMethods() 和 getDeclaredMethods() 的区别?
  • 如何动态调用方法?私有方法需要什么额外步骤?
  • 注解 + 反射组合能给一个实际例子吗?
  • 为什么说反射有性能代价?

旅人笔记

反射 = 程序运行时查看自己。注解 + 反射 = Java 框架的基石。Class.forName() 用字符串加载类。getMethod() + invoke() 动态调用。setAccessible(true) 绕过封装——框架用它,你的代码不要。反射比直接调用慢,业务代码优先用直接调用。

下一步

注解让你贴标签,反射让你读标签——但这些都是"单线程世界"的能力。你的图书馆系统上线了,一百个读者同时查询——一个在写一个在读,数据就乱了。你以前写的代码都假设同一时间只有一个人在执行。现实世界不是这样的。

前往第14章:并发编程基础

Built with VitePress | Software Systems Atlas