乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

元注解详解

大翔站在白板前,指着屏幕上密密麻麻的注解代码说道:"注解不是魔法,它是元数据。而控制注解行为的,正是'元注解'——注解的注解。"

白歌推了推眼镜:"就像我们公司的规章制度,规章制度本身也需要被定义适用范围和有效期。"

小崔举手:"大翔哥,那 @Override 是怎么让编译器知道我在重写方法的?"

大翔笑了:"问得好。@Override 之所以能在编译期生效,是因为它的 @Retention 是 SOURCE。这就是元注解的力量——它定义了注解的'生存法则'。"

朱璐在旁边敲着键盘:"所以元注解就是注解的'配置中心'?"


一、定义表

元注解作用JDK版本取值示例
@Retention指定注解保留到哪个阶段JDK 5SOURCE, CLASS, RUNTIME
@Target指定注解可以修饰哪些元素JDK 5TYPE, METHOD, FIELD, PARAMETER 等
@Documented标记注解是否出现在Javadoc中JDK 5无参数,标记注解
@Inherited标记注解是否被自动继承JDK 5无参数,标记注解
@Repeatable允许同一个元素重复标注同一注解JDK 8value = 容器注解.class

二、Mermaid 类图:元注解体系结构


三、Mermaid 流程图:注解处理的两个阶段


四、@Retention 详解

@Retention 定义了注解的"生命周期",通过 RetentionPolicy 枚举指定:

RetentionPolicy含义典型场景
SOURCE仅保留在源码中,编译时丢弃@Override, @SuppressWarnings, @SafeVarargs
CLASS保留到字节码中,但运行时不可通过反射获取Lombok 注解(如 @Data),JSR 308 类型注解
RUNTIME保留到运行时,反射可读取@Test, @Autowired, @Entity

五、@Target 详解

@Target 限制注解可以修饰的程序元素,通过 ElementType 枚举数组指定:

ElementType可修饰的元素示例
TYPE类、接口、枚举、注解@Entity, @Service
FIELD成员变量(含枚举常量)@Autowired, @Column
METHOD方法@Override, @Test
PARAMETER方法参数@RequestParam, @NotNull
CONSTRUCTOR构造器@Inject
LOCAL_VARIABLE局部变量@SuppressWarnings 可用于局部变量
ANNOTATION_TYPE注解类型元注解本身
PACKAGE包声明@NonNullApi
TYPE_PARAMETER类型参数声明<@NonNull T>
TYPE_USE任何类型使用处new @NonNull Object()

六、@Documented 与 @Inherited

6.1 @Documented

当一个注解被 @Documented 修饰时,使用该注解的元素的 Javadoc 文档中会包含该注解信息。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CompanyDocument {
    String name();
    String date();
}

6.2 @Inherited

被 @Inherited 修饰的注解,其子类会自动继承父类的该注解。注意:@Inherited 仅对类继承有效,对接口继承无效。

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface InheritedAnnotation {
    String value();
}

七、@Repeatable 详解(JDK 8 新增)

JDK 8 之前,同一个位置无法重复标注同一个注解。JDK 8 引入 @Repeatable 解决了这一痛点。

@Repeatable 需要一个"容器注解"来包裹多个重复注解:

// 容器注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Schedules {
    Schedule[] value();  // 必须返回重复注解的数组
}

// 重复注解
@Repeatable(Schedules.class)  // 指定容器注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Schedule {
    String cron();
    String desc() default "";
}

八、注解元素类型(Annotation Element)

注解中可以定义的元素类型必须是以下之一:

  • 基本数据类型(int, float, boolean 等)
  • String
  • Class
  • 枚举
  • 其他注解类型
  • 以上类型的数组

关键规则:

  • 元素声明类似方法声明,不能带参数
  • 可以用 default 指定默认值
  • 如果只有一个元素且名为 value,使用时可以省略 value=
public @interface Employee {
    int id();
    String name() default "新员工";
    double salary() default 8888.88;
    String[] skills() default {};
}

九、完整代码示例

示例1:飞翔科技自定义@Validate校验注解

import java.lang.annotation.*;
import java.lang.reflect.Field;

/**
 * 飞翔科技自定义校验注解 —— 配合元注解配置
 */

// 1. 标记注解:标识字段需要校验
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@interface NotNull {
    String message() default "字段不能为 null";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@interface Min {
    int value() default 0;
    String message() default "值太小";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@interface Max {
    int value() default 99999;
    String message() default "值太大";
}

// 2. 组合校验注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@interface Validate {
    boolean notNull() default false;
    int min() default Integer.MIN_VALUE;
    int max() default Integer.MAX_VALUE;
}

// 3. 飞翔科技员工类
class FeixiangEmployee {
    @NotNull(message = "员工ID不能为空")
    private Integer id;

    @NotNull(message = "员工姓名不能为空")
    private String name;

    @Min(value = 3000, message = "薪资不能低于3000")
    @Max(value = 50000, message = "薪资不能超过50000")
    private double salary;

    @Validate(notNull = true, min = 18, max = 65)
    private int age;

    public FeixiangEmployee(Integer id, String name, double salary, int age) {
        this.id = id;
        this.name = name;
        this.salary = salary;
        this.age = age;
    }

    @Override
    public String toString() {
        return "员工[id=" + id + ", name=" + name
                + ", salary=" + salary + ", age=" + age + "]";
    }
}

// 4. 校验引擎
class Validator {
    public static void validate(Object obj) throws Exception {
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            Object value = field.get();

            // 处理 @NotNull
            NotNull notNull = field.getAnnotation(NotNull.class);
            if (notNull != null && value == null) {
                throw new RuntimeException(notNull.message());
            }

            // 处理 @Min
            Min min = field.getAnnotation(Min.class);
            if (min != null && value instanceof Number) {
                double numVal = ((Number) value).doubleValue();
                if (numVal < min.value()) {
                    throw new RuntimeException(min.message() + ",当前值: " + numVal);
                }
            }

            // 处理 @Max
            Max max = field.getAnnotation(Max.class);
            if (max != null && value instanceof Number) {
                double numVal = ((Number) value).doubleValue();
                if (numVal > max.value()) {
                    throw new RuntimeException(max.message() + ",当前值: " + numVal);
                }
            }
        }
        System.out.println("✓ " + obj + " 校验通过!");
    }
}

// 5. 测试主类
public class FeixiangAnnotationDemo {
    public static void main(String[] args) {
        System.out.println("========== 飞翔科技注解校验系统 ==========\n");

        // 测试1:合法员工
        FeixiangEmployee emp1 = new FeixiangEmployee(1001, "大翔", 18888.88, 28);
        try {
            Validator.validate(emp1);
        } catch (Exception e) {
            System.out.println("✗ 校验失败: " + e.getMessage());
        }

        // 测试2:薪资过低
        FeixiangEmployee emp2 = new FeixiangEmployee(1002, "小崔", 2500.0, 22);
        try {
            Validator.validate(emp2);
        } catch (Exception e) {
            System.out.println("✗ 校验失败: " + e.getMessage());
        }

        // 测试3:姓名为空
        FeixiangEmployee emp3 = new FeixiangEmployee(1003, null, 8888.88, 30);
        try {
            Validator.validate(emp3);
        } catch (Exception e) {
            System.out.println("✗ 校验失败: " + e.getMessage());
        }

        // 测试4:年龄超限
        FeixiangEmployee emp4 = new FeixiangEmployee(1004, "朱璐", 12000.0, 70);
        try {
            Validator.validate(emp4);
        } catch (Exception e) {
            System.out.println("✗ 校验失败: " + e.getMessage());
        }

        System.out.println("\n========== End ==========");
    }
}

运行输出:

========== 飞翔科技注解校验系统 ==========

✓ 员工[id=1001, name=大翔, salary=18888.88, age=28] 校验通过!
✗ 校验失败: 薪资不能低于3000,当前值: 2500.0
✗ 校验失败: 员工姓名不能为空
✗ 校验失败: 年龄不能超过65,当前值: 70.0

========== End ==========

示例2:飞翔科技@Schedule定时任务注解(@Repeatable)

import java.lang.annotation.*;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 飞翔科技定时任务调度系统 —— 演示 @Repeatable 用法
 */

// 1. 容器注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface TaskSchedules {
    TaskSchedule[] value();
}

// 2. 可重复的定时任务注解
@Repeatable(TaskSchedules.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface TaskSchedule {
    String cron();          // cron 表达式
    String desc() default ""; // 任务描述
    int priority() default 5; // 优先级 1-10
}

// 3. 任务处理器
class TaskProcessor {

    @TaskSchedule(cron = "0 0 9 * * MON-FRI", desc = "早晨站会提醒", priority = 8)
    @TaskSchedule(cron = "0 30 12 * * MON-FRI", desc = "午休提醒", priority = 3)
    public void dailyMeetingReminder() {
        System.out.println("  [执行] 日常会议提醒任务");
    }

    @TaskSchedule(cron = "0 0 0 1 * *", desc = "月初财务报表生成", priority = 10)
    @TaskSchedule(cron = "0 0 0 15 * *", desc = "月中财务复核", priority = 9)
    @TaskSchedule(cron = "0 0 0 L * *", desc = "月末财务结算", priority = 10)
    public void financialReport() {
        System.out.println("  [执行] 财务报告生成任务");
    }

    @TaskSchedule(cron = "0 0 2 * * SUN", desc = "数据库备份", priority = 7)
    public void databaseBackup() {
        System.out.println("  [执行] 数据库备份任务");
    }

    @TaskSchedule(cron = "0 */5 * * * *", desc = "系统健康检查", priority = 6)
    @TaskSchedule(cron = "0 */1 * * * *", desc = "用户活跃度统计", priority = 4)
    public void systemMonitor() {
        System.out.println("  [执行] 系统监控任务");
    }
}

// 4. 调度器
class Scheduler {
    public static void scanAndPrintTasks(Object obj) {
        Class<?> clazz = obj.getClass();
        System.out.println("扫描类: " + clazz.getSimpleName());
        System.out.println("=".repeat(60));

        for (Method method : clazz.getDeclaredMethods()) {
            // 使用 getAnnotationsByType 读取 @Repeatable 注解(JDK 8 新增方法)
            TaskSchedule[] schedules = method.getAnnotationsByType(TaskSchedule.class);

            if (schedules.length > 0) {
                System.out.println("\n方法: " + method.getName());
                System.out.println("-".repeat(40));
                for (TaskSchedule schedule : schedules) {
                    System.out.printf("  ├─ Cron: %-25s | 描述: %-15s | 优先级: %d\n",
                            schedule.cron(), schedule.desc(), schedule.priority());
                }
            }
        }

        // 也展示 getAnnotation 获取容器注解的方式
        System.out.println("\n" + "=".repeat(60));
        System.out.println("(以下展示通过容器注解 TaskSchedules 获取)\n");
        for (Method method : clazz.getDeclaredMethods()) {
            TaskSchedules container = method.getAnnotation(TaskSchedules.class);
            if (container != null) {
                System.out.println("方法 " + method.getName() + " 有 "
                        + container.value().length + " 个定时任务");
            }
        }
    }
}

// 5. 主程序
public class FeixiangScheduleDemo {
    public static void main(String[] args) {
        System.out.println("========== 飞翔科技定时任务调度系统 ==========\n");

        TaskProcessor processor = new TaskProcessor();
        Scheduler.scanAndPrintTasks(processor);

        System.out.println("\n" + "=".repeat(60));
        System.out.println("飞翔科技的总监 Frank 说道:");
        System.out.println("\"有了 @Repeatable,一个方法可以挂多个定时任务,代码清爽多了!\"");
        System.out.println("\n========== End ==========");
    }
}

运行输出:

========== 飞翔科技定时任务调度系统 ==========

扫描类: TaskProcessor
============================================================

方法: dailyMeetingReminder
----------------------------------------
  ├─ Cron: 0 0 9 * * MON-FRI          | 描述: 早晨站会提醒    | 优先级: 8
  ├─ Cron: 0 30 12 * * MON-FRI        | 描述: 午休提醒        | 优先级: 3

方法: financialReport
----------------------------------------
  ├─ Cron: 0 0 0 1 * *               | 描述: 月初财务报表生成  | 优先级: 10
  ├─ Cron: 0 0 0 15 * *              | 描述: 月中财务复核      | 优先级: 9
  ├─ Cron: 0 0 0 L * *               | 描述: 月末财务结算      | 优先级: 10

方法: databaseBackup
----------------------------------------
  ├─ Cron: 0 0 2 * * SUN             | 描述: 数据库备份        | 优先级: 7

方法: systemMonitor
----------------------------------------
  ├─ Cron: 0 */5 * * * *             | 描述: 系统健康检查      | 优先级: 6
  ├─ Cron: 0 */1 * * * *             | 描述: 用户活跃度统计    | 优先级: 4

============================================================
(以下展示通过容器注解 TaskSchedules 获取)

方法 dailyMeetingReminder 有 2 个定时任务
方法 financialReport 有 3 个定时任务
方法 systemMonitor 有 2 个定时任务

============================================================
飞翔科技的总监 Frank 说道:
"有了 @Repeatable,一个方法可以挂多个定时任务,代码清爽多了!"

========== End ==========

十、易错场景

易错1:@Retention 设为 CLASS 导致反射获取不到

// 错误示例
@Retention(RetentionPolicy.CLASS)   // ← 编译后保留但运行时不可反射访问!
@Target(ElementType.METHOD)
@interface MyLog {}

public class ErrorDemo1 {
    @MyLog
    public void doWork() {}

    public static void main(String[] args) throws Exception {
        Method m = ErrorDemo1.class.getMethod("doWork");
        MyLog anno = m.getAnnotation(MyLog.class);
        System.out.println(anno);  // 输出: null ← 注解在运行时消失了!
    }
}

正确做法:如果需要在运行时通过反射读取注解,必须使用 RetentionPolicy.RUNTIME。

@Retention(RetentionPolicy.RUNTIME)  // ← 正确:运行时可见
@Target(ElementType.METHOD)
@interface MyLog {}

易错2:@Inherited 不会通过接口继承

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface FeixiangAnnotation {}

@FeixiangAnnotation
interface BaseService {}

class UserService implements BaseService {}  // UserService 不会继承 @FeixiangAnnotation

// 验证
public class ErrorDemo2 {
    public static void main(String[] args) {
        // 接口继承:输出 null
        System.out.println(UserService.class.getAnnotation(FeixiangAnnotation.class)); // null!
        // @Inherited 只对类继承生效,对接口继承无效
    }
}

易错3:@Repeatable 容器注解 value() 返回类型不匹配

// 错误示例
@interface BugReports {
    String[] value();  // ← 错误!必须返回 BugReport[],不能是 String[]
}

@Repeatable(BugReports.class)
@interface BugReport {
    String value();
}
// 编译错误:containing annotation type must declare value() element with return type BugReport[]

正确做法:容器注解的 value() 方法必须返回被包含注解类型的数组。

易错4:注解元素不能是包装类型和复杂对象

public @interface WrongAnnotation {
    Integer count();       // ← 编译错误!不能用包装类型,只能用 int
    List<String> tags();   // ← 编译错误!不能用集合类型
    Date updateDate();     // ← 编译错误!只能用 String、Class、枚举、注解等基础类型
}

// 正确做法
public @interface CorrectAnnotation {
    int count();                 // ✓ 基本类型
    String[] tags();             // ✓ String 数组
    Class<?> entityClass();      // ✓ Class 类型
    ElementType type();          // ✓ 枚举类型
}

十一、面试考点

考点1:@Retention 的三种策略分别在什么场景下使用?

答:

  • SOURCE:仅编译期需要,如 @Override 给编译器检查方法重写是否正确;@SuppressWarnings 抑制编译器警告。这类注解编译后就不需要了。
  • CLASS:需要保留在字节码中但运行时无需通过反射访问。典型如 Lombok 的 @Data、@Getter——它们需要在编译期修改字节码,但运行期不需要感知。这也是默认策略。
  • RUNTIME:运行时需要通过反射读取的注解,如 Spring 的 @Autowired、JUnit 的 @Test、Hibernate 的 @Entity。几乎所有框架注解都用 RUNTIME。

考点2:@Repeatable 的容器注解必须满足什么条件?

答:

  1. 容器注解的 value() 方法必须返回被重复注解类型的数组。
  2. 容器注解的 @Retention 和 @Target 必须至少与被重复注解一样宽泛。
  3. 使用 getAnnotationsByType(Xxx.class)(JDK 8 新增)来读取重复注解,传统 getAnnotation() 只能读到容器注解。

考点3:@Inherited 的继承机制有什么限制?

答:

  1. 只对类的继承生效——子类继承父类的 @Inherited 注解。
  2. 对接口继承无效——子接口不会继承父接口的 @Inherited 注解。
  3. 对接口实现无效——实现类不会从接口继承 @Inherited 注解。
  4. 对方法重写无效——子类重写的方法不会继承父类方法上的 @Inherited 注解(@Inherited 只能标注类级别)。

考点4:ElementType.TYPE_USE 和 TYPE_PARAMETER 是什么?JDK 8 新增的有什么意义?

答: JDK 8 新增了两个 ElementType 值,用于支持 JSR 308(类型注解):

  • TYPE_PARAMETER:可用在类型参数声明处,如 class Box<@NonNull T>。
  • TYPE_USE:可用在任何使用类型的地方,如 new @NonNull Object()、(@NonNull String) obj。

这为 Checker Framework 等静态分析工具提供了基础,使得类型检查可以在编译期进行(如空安全检查 @Nullable、@NonNull)。


十二、JDK 8 元注解速查表

元注解属性JDK 8 新增常用场景
@RetentionRetentionPolicy value()-控制注解生命周期
@TargetElementType[] value()TYPE_PARAMETER, TYPE_USE限制注解适用范围
@Documented--Javadoc 文档中可见
@Inherited--子类自动继承注解
@RepeatableClass<? extends Annotation> value()✓同一位置重复标注
AnnotatedElement.getAnnotationsByType()-✓ (新增方法)读取 @Repeatable 注解

白歌合上笔记本总结道:"元注解就像是给注解编程。@Retention 决定'活多久',@Target 决定'用在哪',@Repeatable 允许'反复说'——掌握了这五个元注解,就掌握了注解体系的底层逻辑。"

大翔点点头:"没错。Spring Boot 的自动配置、JUnit 的测试框架、Lombok 的代码生成,底层都是元注解在发挥作用。它们是 Java 元编程的基石。"

黄俪(HR)路过,插了一句:"你们能不能用元注解给我写个自动排班系统?"

众人笑:"这个需求我们记下了!"

上一页
注解基础
下一页
自定义注解