元数据卡
- 前置知识:第13章前半(注解)、第7章(面向对象基础)
- 预计时间:35 分钟
- 完成标志:能通过 Class 对象获取类的字段和方法;能动态调用方法;理解注解 + 反射的组合原理
标签贴上了。然后谁来读它们? 如果有人拿一把剑走进工坊,剑柄上刻着字,你得有一个工具来认字。Java 里读标签的工具叫反射——程序在运行时查看自己的结构。
第三幕:Class 对象——类的档案袋
JVM 加载一个类时,会在堆里创建唯一的 Class<?> 对象。获取它有三种方式:
// 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
}
}预期输出:
true
true
String
class java.lang.ObjectClass.forName() 最强大——类名可从配置文件读取,写代码时不需要知道它。
查看结构——方法、字段、构造器
拿到档案袋后,翻翻里面有什么:
// 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());
}
}预期输出(简化):
公有方法:
getName wait equals toString getClass notify
本类字段(含私有):
String name
int age关键区别:getMethods() 拿到所有 public 方法(含继承),getDeclaredMethods() 拿到本类所有方法(含私有,不含继承)。字段同理。
第四幕:动态调用——运行时决定一切
你写代码时不知道类名和方法名,运行时再确定:
// 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
私有方法也能调——但不该随便用:
// 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 现在配合反射来使用:
// 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");
}
}预期输出:
[DEBUG] 执行 placeOrder
下单中...
[DEBUG] 完成这就是 AOP 的雏形:只贴标签,处理器自动拦截。Spring 的 @Transactional、@GetMapping 背后都是同样的原理。
性能警告
反射比直接调用慢 1-2 个数量级。业务代码不要用反射,框架才用。高频调用要缓存 Method 对象,不要循环里反复查。
验收标准
- 获取 Class 对象的三种方式是什么?
- getMethods() 和 getDeclaredMethods() 的区别?
- 如何动态调用方法?私有方法需要什么额外步骤?
- 注解 + 反射组合能给一个实际例子吗?
- 为什么说反射有性能代价?
旅人笔记
反射 = 程序运行时查看自己。注解 + 反射 = Java 框架的基石。Class.forName() 用字符串加载类。getMethod() + invoke() 动态调用。setAccessible(true) 绕过封装——框架用它,你的代码不要。反射比直接调用慢,业务代码优先用直接调用。
下一步
注解让你贴标签,反射让你读标签——但这些都是"单线程世界"的能力。你的图书馆系统上线了,一百个读者同时查询——一个在写一个在读,数据就乱了。你以前写的代码都假设同一时间只有一个人在执行。现实世界不是这样的。