22_Java 注解反射与运行时类型
反射调用、注解保留策略、泛型擦除及框架运行时边界。
Java 注解、反射与运行时类型
学习目标:理解框架如何在运行时读取类信息,以及注解、反射和泛型擦除各自的边界。基线为 Java 21。
1. 编译期类型与运行时对象
Java 源码经编译成为字节码。运行时可以通过 Class<?> 查看类名、构造器、字段和方法,也能用反射调用方法。反射(reflection)适合框架、序列化和工具,但业务代码若大量依赖字符串查找方法,重构时更容易漏掉错误。
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws ReflectiveOperationException {
Class<String> type = String.class;
Method method = type.getMethod("substring", int.class);
String result = (String) method.invoke("hello", 2);
System.out.println(result); // llo
}
}
getMethod 查找可访问的公共方法;invoke 可能因目标方法自身抛异常而包装为 InvocationTargetException。反射调用通常比普通调用更难阅读,且依赖运行时权限与模块开放规则,不应为普通业务分支滥用。
2. 注解的三个维度
注解(annotation)是在代码元素上附加元数据。定义注解时要考虑放在哪里(@Target)、保留到何时(@Retention)、由谁解释。SOURCE 只在源码阶段,CLASS 留在字节码,RUNTIME 可在运行时经反射读取。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Audited { String action(); }
class NoteService {
@Audited(action = "create-note")
void create() {}
}
注解本身不会自动执行审计逻辑;必须有编译器、注解处理器或运行时框架读取它。Spring 的 @Transactional 也依赖框架代理等机制,不能把“写了注解”直接等同于“任何调用都会开启事务”。
3. 泛型擦除与框架映射
List<String> 的泛型参数主要在编译期检查;运行时不能写 value instanceof List<String>。反射可以读取某些声明位置的泛型签名,但不能从一个空 ArrayList 实例恢复实际元素类型。JSON 反序列化复杂泛型时,库可能要求显式类型描述。
4. 实践与易错点
- 用反射列出
NoteService的方法与@Audited的action;如果去掉RUNTIME保留策略,观察运行时读取结果。 - 解释普通方法调用、反射调用和框架代理调用分别在何处发现错误。
- 不要随意
setAccessible(true)绕过封装;模块系统与安全策略可能限制访问,而且会让代码依赖内部实现。
易错点:运行时注解只是元数据;没有处理器或框架读取时,不会产生业务行为。