bigywater.tech
Java 内部原理 · 61

注解 RetentionPolicy 真实验证:反射到底能读到哪几种保留策略

@Retention 的三个取值 SOURCE/CLASS/RUNTIME 决定了注解能活多久,但很少有人真的写一个同时标注三种注解的类,用反射去验证运行时到底能看见谁。这篇真实跑一遍。

反射真实只读到 1 个
真实验证:同时标注 SOURCE/CLASS/RUNTIME 三个注解的类,getAnnotations() 真实只返回 RUNTIME 那一个
SOURCE 注解定义仍在
真实验证:@Retention(SOURCE) 的注解类型本身依然编译成了一个真实存在的 class 文件,消失的只是"标注行为"的痕迹

上一篇:位运算与 HashMap 容量真实验证

真实验证:三种保留策略,反射真实只看到一种

给同一个类同时标注三个不同保留策略的自定义注解,用 getAnnotations() 看运行时真实能读到几个。

AnnotationRetentionDemo.java @Retention(RetentionPolicy.SOURCE) @interface SourceOnly {} @Retention(RetentionPolicy.CLASS) @interface ClassOnly {} @Retention(RetentionPolicy.RUNTIME) @interface RuntimeVisible {} @SourceOnly @ClassOnly @RuntimeVisible static class Target {} Target.class.getAnnotations();
真实实测
反射真实能读到的注解个数: 1
  真实读到: @AnnotationRetentionDemo$RuntimeVisible()
Target.class.isAnnotationPresent(RuntimeVisible.class): true
Target.class.isAnnotationPresent(ClassOnly.class): false

真实结果验证了三种策略的层层递进关系:Target 类源码上真实标注了三个注解,但反射只能真实看到 RuntimeVisible 这一个。SourceOnly 在编译成 class 文件那一步就真实被丢弃,连字节码里都没有痕迹,专门给注解处理器(APT)在编译期用;ClassOnly 真实被写进了 class 文件(可以用 javap -v 在常量池里看到),但 JVM 加载类时不会把它暴露给标准反射 API,常用于字节码增强工具;只有 RUNTIME 真实被 JVM 加载进内存并保留给反射查询,这也是 @Override(SOURCE)、@Deprecated(RUNTIME)这些内置注解各自选择不同策略的原因。

真实验证:SOURCE 注解的"定义"本身没有消失

容易搞混的一点:"SOURCE 保留策略"指的是"标注这个动作"在编译后消失,不是这个注解类型本身消失。

真实实测
SourceOnly 注解类本身仍然存在(注解定义不会因为SOURCE保留而消失):
interface AnnotationRetentionDemo$SourceOnly
真实区分两件事。@Retention 影响的是"某个具体标注实例(比如 Target 类头上的那个 @SourceOnly)在编译后是否还能被查询到",而不是"注解类型 @interface SourceOnly 本身是否存在"。真实验证证明:即使这个注解的保留策略是 SOURCE,SourceOnly 这个接口(所有注解在字节码层面本质上都是接口)依然被真实编译成了一个 class 文件,可以正常被 Class.forName() 加载——只是"谁被它标注过"这个信息在编译后不可查了。

分步演示:三种保留策略是怎么逐层消失的

每一行都是真实反射查询得到的结果。

同一个 Target 类头上的三个注解,反射真实只能看到 RUNTIME 那一个
这一步新验证的查询

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,反射结果重复运行完全一致。
    • 和反射调用开销那篇一起看,能更完整理解反射能"看到"什么、看不到什么的边界。
    • 没有涉及:@Target 元注解对注解可标注位置的真实约束、注解处理器(Annotation Processor)在编译期如何真实消费 SOURCE 保留的注解——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号