@Retention 的三个取值 SOURCE/CLASS/RUNTIME 决定了注解能活多久,但很少有人真的写一个同时标注三种注解的类,用反射去验证运行时到底能看见谁。这篇真实跑一遍。
给同一个类同时标注三个不同保留策略的自定义注解,用 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 保留策略"指的是"标注这个动作"在编译后消失,不是这个注解类型本身消失。
SourceOnly 注解类本身仍然存在(注解定义不会因为SOURCE保留而消失): interface AnnotationRetentionDemo$SourceOnly
每一行都是真实反射查询得到的结果。