Skip to content

注解

概述

注解(Annotation)是 Java 中为代码附加元数据的机制:它本身不直接改变程序逻辑,而是为类、方法、字段、参数等提供「说明信息」。这些信息可在编译期被编译器使用(如 @Override 检查重写)、在运行时通过 反射 被框架读取(如 JUnit 的 @Test、Spring 的 @Component),从而实现配置、校验、代码生成等能力。

通俗理解:注解就像贴在代码上的「标签」——声明「这是什么」「有什么约束」;谁使用这些标签(编译器或反射),谁就按约定处理。学习注解有助于理解现代 Java 框架(如 Spring、JUnit、Lombok)的工作方式,并与 枚举反射 形成完整知识链。

前置建议

已掌握 类与对象接口枚举反射 可在学完注解后再深入,用于「读取注解」。


为什么需要注解

  • 元数据与配置:在不侵入业务代码的前提下,声明类/方法的用途、约束或配置(如「这是测试方法」「这是 Web 控制器」)。
  • 编译器检查:部分注解由编译器处理,如 @Override 确保正确重写父类方法,@SuppressWarnings 抑制警告。
  • 运行时行为:框架通过反射读取注解,驱动行为(如 JUnit 执行带 @Test 的方法、Spring 扫描带 @Component 的类并创建 Bean)。
  • 替代部分 XML/配置文件:注解可把配置写在代码旁,减少分散的配置文件,提高可读性和维护性。

基本语法

使用注解

使用注解时,在目标元素前加 @注解类型,必要时在括号内写属性(键值对)。

java
// 无属性的注解
@Override
public String toString() {
    return "Example";
}

// 单属性且名为 value 时可省略键名
@SuppressWarnings("unchecked")
List list = new ArrayList();

// 多属性或非 value 属性需写明键名
@RequestMapping(path = "/user", method = RequestMethod.GET)
public String getUser() {
    return "user";
}

注解可标注的目标

注解可以标注在:类、接口、枚举、方法、构造方法、字段、参数、局部变量、类型参数 等。具体能标在哪里,由注解定义时的 @Target 元注解决定。


元注解

元注解是「用来修饰注解的注解」,用于定义注解的作用目标、保留策略等。定义自定义注解时经常用到。

元注解作用
@Target指定注解可标注在哪些元素上(TYPE、METHOD、FIELD、PARAMETER 等)
@Retention指定注解保留到何时:SOURCE(仅源码)、CLASS(字节码)、RUNTIME(运行时可用)
@Documented是否将注解包含在 Javadoc 中
@Inherited子类是否继承父类上的该注解
@Repeatable同一位置是否可重复使用该注解(JDK 8+)

示例:读懂 @Override

java
// 简化理解:@Override 的语义
@Target(ElementType.METHOD)   // 只能标在方法上
@Retention(RetentionPolicy.SOURCE)  // 仅编译期使用,不会进入字节码
public @interface Override {
}
  • SOURCE:编译器检查方法是否真的重写了父类/接口方法,检查完即丢弃,不保留到运行时。
  • RUNTIME:若要通过反射在运行时读取注解,必须使用 RetentionPolicy.RUNTIME(如 JUnit、Spring 的注解)。

内置注解

JDK 自带常用注解,建议先熟悉其用途。

注解作用
@Override标记方法重写父类或接口方法,编译器会检查签名是否匹配
@Deprecated标记已过时,调用处会产生编译警告,建议配合 Javadoc 说明替代方案
@SuppressWarnings抑制指定类型的编译警告(如 "unchecked""deprecation"
@FunctionalInterface标记接口为函数式接口(仅一个抽象方法),供 Lambda 使用
@SafeVarargs声明可变参数用法是安全的,抑制相关警告
java
// @Override:确保重写正确,避免拼写或参数错误
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    User user = (User) o;
    return Objects.equals(name, user.name);
}

// @Deprecated:标记过时方法,调用时会有警告
@Deprecated(since = "2.0", forRemoval = true)
public void oldApi() {
    // 建议使用 newApi()
}

// @SuppressWarnings:在已知安全时抑制警告
@SuppressWarnings("unchecked")
public <T> T getFirst(List list) {
    return (T) list.get(0);
}

提示

@Override 建议在重写父类或实现接口方法时始终显式写上,便于编译器帮你发现签名错误。


自定义注解

定义注解

使用 @interface 定义注解;内部可声明属性(无参方法形式),可指定默认值。注解本质是一种特殊的接口,编译后生成 class 文件。

java
import java.lang.annotation.*;

// 仅用于方法,运行时保留以便反射读取
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Author {
    String name();           // 必填属性
    String date() default "2026-01-01";  // 可选,有默认值
}

// 使用
public class MyService {
    @Author(name = "Zhang", date = "2026-02-01")
    public void doSomething() {
        // ...
    }
}

属性类型限制

注解属性类型可以是:基本类型、String、Class、枚举、注解,以及上述类型的数组。不能是包装类、泛型等复杂类型。

java
public enum Level {
    LOW, MEDIUM, HIGH
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Config {
    String name();
    Class<?> handler() default Object.class;
    Level level() default Level.MEDIUM;
    String[] tags() default {};
}

使用示例

示例 1:标记与说明(RUNTIME + 反射)

定义一个运行时保留的注解,并在简单「测试运行器」中通过反射只执行带该注解的方法,模拟 JUnit 的 @Test 思路。

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RunTest {
    String value() default "";  // 可选描述
}

public class Calculator {
    @RunTest("加法")
    public int add(int a, int b) {
        return a + b;
    }

    public int sub(int a, int b) {
        return a - b;
    }
}

// 简单「测试运行器」:只执行带 @RunTest 的方法
public static void main(String[] args) throws Exception {
    Calculator calc = new Calculator();
    for (Method m : Calculator.class.getDeclaredMethods()) {
        if (m.isAnnotationPresent(RunTest.class)) {
            RunTest rt = m.getAnnotation(RunTest.class);
            System.out.println("Run: " + m.getName() + " - " + rt.value());
            Object result = m.invoke(calc, 2, 3);
            System.out.println("  result: " + result);  // 5
        }
    }
}

信息

反射读取注解的典型用法:method.isAnnotationPresent(RunTest.class) 判断是否存在该注解,method.getAnnotation(RunTest.class) 获取注解实例并读取其属性。详见 反射入门

示例 2:校验约束(结合枚举)

用注解声明「允许的角色」,运行时校验当前用户是否在允许列表中,常用于权限、状态校验等场景。

java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AllowedRoles {
    Role[] value();  // 枚举数组
}

public enum Role {
    ADMIN, USER, GUEST
}

public class UserService {
    @AllowedRoles({ Role.ADMIN, Role.USER })
    public void viewProfile() {
        // 仅 ADMIN、USER 可访问
    }
}

注意事项

  • 保留策略:若要在运行时通过反射读取注解,必须使用 @Retention(RetentionPolicy.RUNTIME);仅给编译器或注解处理器用时可使用 SOURCE 或 CLASS。
  • 作用目标@Target 不写则默认可标在任意位置,但建议明确指定,避免误用。
  • 属性命名:单属性且命名为 value 时,使用时可以省略键名,直接写 @MyAnno("xxx")
  • 注解不替代业务逻辑:注解只提供元数据;真正「做事」的是编译器或运行时代码(如反射),不要误以为加注解就会自动生效。
  • 慎用 @SuppressWarnings:只在确认无问题时局部抑制警告,避免大面积使用掩盖潜在错误。

注意

自定义注解若要在运行时被读取,除了 RetentionPolicy.RUNTIME,还需确保注解类型本身被 JVM 加载(通常只要使用到标注了该注解的类/方法即可)。

提示

框架中常见注解(如 Spring 的 @Component@Autowired,JUnit 的 @Test)都是 RUNTIME 保留,由框架在启动或运行测试时通过反射扫描并处理。学完 反射 后,可以自己实现一个简单的「扫描带某注解的类并执行」的小框架,加深理解。


相关链接

基于 VitePress 构建