4936 字
25 min
0

注解

本文深入讲解 Java 注解(Annotation)的概念与作用,介绍常见内置注解、元注解以及自定义注解的创建与使用方法,帮助初学者系统掌握 Java 注解的开发与实践应用。

什么是注解(Annotation)

注解的概念

注解(Annotation)是自 JDK 1.5 起引入的一种元数据(Metadata)机制,它允许开发者为程序元素(如类、方法、字段、接口、包等)添加结构化的附加信息。这些信息不直接影响程序的运行逻辑,但可以被编译器、开发工具或运行时框架读取并处理,从而实现诸如编译检查、代码生成、配置管理、行为增强等功能。

简单来说,注解是贴在代码上的“标签”,它本身不执行任何操作,但能被工具识别并据此做出决策。

注解和注释

  1. 注释 (Comment):是程序员在源码中编写的说明性文字,旨在提升代码的可读性与可维护性。这些内容仅供开发者阅读,在程序编译阶段会被完全忽略,不会对程序的运行产生任何影响。
  2. 注解 (Annotation):是一种嵌入在代码中的元数据(metadata),它以结构化的形式为编译器、开发工具或运行时环境提供附加信息与指令。程序会根据预设的规则读取并处理这些注解,从而实现在编译时检查、代码生成或运行时注入等自动化行为。
项目注解(Annotation)注释(Comment)
定义方式使用 @ 声明,如 @Override使用 ///* ... *//** ... */ 等符号
编译影响可以参与编译和运行时处理不会被编译器编译到字节码中
存在阶段源代码、字节码、运行时(根据保留策略不同)仅存在于源代码中
功能提供元信息供工具、编译器、框架处理仅用于增加代码可读性
是否可反射访问可以通过反射 API 获取无法访问

注解的作用

注解的主要作用体现在 编译期 和 运行期,常见作用包括:

阶段作用说明典型示例
编译期编译检查验证代码正确性,防止常见错误@Override@Deprecated
代码生成由 APT 工具生成新类或资源文件Lombok 的 @Data@Builder
警告抑制控制编译器警告输出@SuppressWarnings("unchecked")
运行期反射处理运行时读取注解并执行特定逻辑Spring 的 @RequestMapping@Transactional
依赖注入框架根据注解自动装配 Bean 或服务@Autowired@Inject
权限控制实现方法级别的安全访问控制@PreAuthorize("hasRole('ADMIN')")
工具支持文档生成将注解信息包含进 Javadoc@Documented
IDE 提示支持智能补全、重构、错误检测等@Nullable@NonNull

Java 内置注解

Java 提供了一些 JDK 内置的注解,主要用于编译器检查、代码提示或运行期处理。

@Deprecated

  1. 作用:标记某个程序元素(类、方法、字段等)已过时,不推荐使用。编译器在其他代码引用该元素时会生成警告信息,提示开发者迁移到新的 API。
  2. 引入版本:JDK 1.5
  3. 升级改进:Java 9 增加 sinceforRemoval 属性
    • since(String):表示该元素自哪个版本开始弃用
    • forRemoval(boolean):表示该元素是否计划在未来移除
  4. 保留策略RUNTIME,可在运行时通过反射判断是否弃用
  5. 源码定义
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
    ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD,
    ElementType.LOCAL_VARIABLE, ElementType.PARAMETER, ElementType.TYPE,
    ElementType.PACKAGE, ElementType.MODULE
})
public @interface Deprecated {
    String since() default "";
    boolean forRemoval() default false;
}
 
  1. 使用示例
public class Example {
 
    /**
     * @deprecated 从 2.1 起弃用,请使用 {@link #newApi()} 替代
     */
    @Deprecated(since = "2.1", forRemoval = true)
    public void oldApi() {
        System.out.println("old");
    }
 
    public void newApi() {
        System.out.println("new");
    }
 
    public static void main(String[] args) {
        Example e = new Example();
        e.oldApi(); // ⚠️ 编译器警告:oldApi() 已弃用
    }
}
 

@Override

  1. 作用:检查方法是否真正覆盖父类或实现接口中的方法。如果方法签名不匹配或可见性不正确,编译器会报错,避免因拼写错误或方法签名错误导致逻辑问题。
  2. 背景
    • JDK 1.5 引入 @Override,最初用于覆盖超类方法
    • 从 Java 6 开始,也可以用于实现接口的方法,因此在实现接口时使用 @Override 是安全且推荐的做法
    • 该注解属于SOURCE 级别,即编译后不会保留到 .class 文件中
  3. 注意事项
    • 只对 实例方法 生效
    • 不能用于 构造方法
    • 用于 类继承或接口实现的方法
  4. 源码定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}
 
  1. 使用示例
class Parent {
    void greet() {
        System.out.println("Hi from Parent");
    }
}
 
class Child extends Parent {
 
    @Override
    void greet() { // 正确覆盖父类方法
        System.out.println("Hello from Child");
    }
 
    // ⚠️ 如果方法签名错误,编译器会报错
    // @Override
    // void greett() { } // compile error: method does not override or implement a method from a supertype
}
 
interface MyInterface {
    void run();
}
 
class Impl implements MyInterface {
 
    @Override
    public void run() { // 正确实现接口方法
        System.out.println("Running...");
    }
}
 

@SuppressWarnings

  1. 作用:告诉编译器在指定范围内 抑制特定类型的编译警告。常用于处理泛型类型不安全操作、已弃用方法调用或未使用变量等场景。
  2. 背景
    • JDK 1.5 引入
    • 属于 SOURCE 级别,编译后不会保留到 .class 文件
    • 通过限制范围使用,可以避免隐藏潜在问题
  3. 适用范围
    • 可应用于类、方法、构造方法、字段、局部变量、参数等
    • 推荐放置在最小范围,只抑制确实需要的警告
  4. 源码定义
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value(); // 指定要抑制的警告名称集合
}
 
  1. 常见参数值
警告名含义
unchecked泛型类型不安全操作(如原始类型、强制转换)
deprecation使用了已弃用的方法或类
rawtypes使用了未指定泛型参数的集合类型
unused未使用的变量、方法或导入
serial实现 Serializable 但未定义 serialVersionUID
all抑制所有警告(不推荐)

注意:避免使用 @SuppressWarnings("all"),容易隐藏真实问题

  1. 使用示例
import java.util.*;
 
public class SuppressExample {
 
    // 只抑制一个警告
    @SuppressWarnings("unchecked")
    public void rawUsage() {
        List list = new ArrayList(); // raw type -> unchecked warning
        list.add("a");
        List<String> strings = (List<String>) list; // unchecked cast
    }
 
    // 抑制多个警告
    @SuppressWarnings({"unchecked", "deprecation"})
    void multi() {
        List list = new ArrayList(); // unchecked
        oldMethod();                  // deprecation
    }
 
    @Deprecated
    public void oldMethod() {}
}
 

@SafeVarargs

  1. 作用:标记可变参数方法(varargs method)或构造器是类型安全的,从而抑制编译器对泛型可变参数的“堆污染”(Heap Pollution)警告。
  2. 背景
    • JDK 1.7 引入
    • 用于 final、static 或 private 方法/构造器
    • 不能用于普通实例方法,因为子类可能重写该方法,编译器无法保证安全性
  3. 堆污染说明
    • 泛型数组在运行时类型信息被擦除
    • 如果向数组中插入不符合泛型约束的对象,可能导致 ClassCastException
    • @SafeVarargs 告诉编译器该方法不会修改可变参数数组,操作类型安全
  4. 源码定义
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
  1. 使用示例
import java.util.Arrays;
 
public class SafeVarargsDemo {
 
    // ✅ 正确用法:只读参数,不修改
    @SafeVarargs
    private static <T> void printAll(T... elements) {
        for (T e : elements) System.out.println(e);
    }
 
    // ⚠️ 错误用法:修改了泛型数组,不能标记为 @SafeVarargs
    // @SafeVarargs
    private static <T> void breakTypeSafety(T... elements) {
        Object[] objs = elements;
        objs[0] = 123;  // 破坏类型安全:原本可能是 String[]
    }
 
    public static void main(String[] args) {
        printAll("A", "B", "C");   // 安全
        // breakTypeSafety("X", "Y", "Z"); // 运行时 ClassCastException
    }
}
 

@FunctionalInterface

  1. 作用:标记一个接口为函数式接口(Functional Interface),编译器会强制检查该接口是否恰好只包含一个抽象方法。

    函数式接口是 Java 8 引入 Lambda 表达式的基础。

  2. 特点

    • 函数式接口:接口中恰好有一个抽象方法(Single Abstract Method,SAM)。
    • 允许有默认方法(default)或静态方法,不会影响函数式接口的定义。
    • 即使没有加 @FunctionalInterface 注解,只要接口满足 SAM 定义,也可以作为函数式接口使用。
    • 加了注解,编译器会检查,如果接口违反了 SAM 规则(抽象方法多于 1 个),编译器会报错。
  3. 源码定义

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
 
  1. 使用示例
@FunctionalInterface
public interface MyFunc {
    void accept(String s);          // 唯一的抽象方法
 
    default void helper() {         // 默认方法,不算抽象
        System.out.println("helper");
    }
    
    // ⚠️ 如果再加一个抽象方法,编译器会报错
    // void another();
}
 

@Generated

  1. 作用:标记某段代码是由工具自动生成的,而非手工编写。主要用于区分手写代码和自动生成代码,方便静态分析工具、IDE 或框架进行处理。
  2. 特点
    • 主要作用是元信息记录,不会影响程序编译或运行。
    • 位于 javax.annotation.processing.Generated 包(Java 9 引入)。
    • 常被代码生成器、注解处理器、框架工具使用。
  3. 源码定义
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({
    ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR,
    ElementType.FIELD, ElementType.PACKAGE
})
public @interface Generated {
    String[] value();          // 生成该代码的工具名称
    String date() default "";  // 生成日期(可选)
    String comments() default ""; // 备注信息(可选)
}
 
  1. 使用示例
import javax.annotation.processing.Generated;
 
@Generated(value = "MyCodeGenTool", date = "2025-09-17", comments = "自动生成类")
public class AutoGeneratedClass {
    public void doWork() {
        System.out.println("工作中...");
    }
}
 

元注解(Meta-Annotation)

  1. 元注解是用于修饰注解的注解,用于规定自定义注解的行为特性,例如:注解的作用范围、保留周期、是否可继承、是否可重复等。
  2. 它们位于 java.lang.annotation 包中。

@Retention

  1. 作用:指定注解的保留策略(生命周期),即注解在什么阶段可被保留和访问。
  2. 可选值
策略说明示例注解
SOURCE仅在源码阶段存在,编译后会被丢弃@Override
CLASS编译进 .class 文件,但运行时不可通过反射访问(默认)@Deprecated
RUNTIME编译进 .class,运行时依然可通过反射访问自定义注解常用
  1. 注意事项:
    • JDK 1.5 引入
    • 通常用于修饰自定义注解
  2. 源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}
 
  1. 示例
import java.lang.annotation.*;
 
@Retention(RetentionPolicy.RUNTIME) // 运行时可通过反射读取
@interface MyAnno {}
 
public class Demo {
    @MyAnno
    public void test() {}
}
 

@Target

  1. 作用:指定注解可以应用到哪些程序元素(类、方法、字段等)。
  2. 典型取值(ElementType 枚举):
ElementType说明备注
TYPE类、接口、枚举常用于类级注解
FIELD成员变量(包括枚举常量)
METHOD方法
PARAMETER方法参数
CONSTRUCTOR构造方法
LOCAL_VARIABLE局部变量
ANNOTATION_TYPE注解类型修饰其他注解
PACKAGE
TYPE_PARAMETER类型参数Java 8+
TYPE_USE类型使用Java 8+
MODULE模块Java 9+
  1. 注意事项

    • JDK 1.5 引入

    • 不写默认可用于所有元素

  2. 源码定义

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}
 
  1. 使用示例
import java.lang.annotation.*;
 
@Target({ElementType.METHOD, ElementType.FIELD})
@interface MyAnno {}
 
public class Demo {
    @MyAnno
    public void test() {}
}
 

@Documented

  1. 作用:标记注解使用的元素在生成 Javadoc 时包含注解信息。
  2. 注意事项
    • JDK 1.5 引入
    • 默认情况下,注解不会出现在 Javadoc 中
    • 常用于公共 API 注解
  3. 源码定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {}
 
  1. 使用示例
import java.lang.annotation.*;
 
@Documented
@interface PublicApi {}
 
/**
 * @PublicApi 注解会出现在 Javadoc 中
 */
@PublicApi
public class Service {}
 

@Inherited

  1. 作用:表示注解可以被子类继承(仅对类有效)。
  2. 注意事项
    • JDK 1.5 引入
    • 只对 @Target(ElementType.TYPE) 注解有效
    • 不会继承到方法、字段、构造器等
    • 仅通过反射读取时可见
  3. 源码定义
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {}
 
  1. 使用示例
import java.lang.annotation.*;
 
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnno {}
 
@MyAnno
class Parent {}
 
class Child extends Parent {}
 
public class Demo {
    public static void main(String[] args) {
        System.out.println(Child.class.isAnnotationPresent(MyAnno.class)); 
        // true:子类继承了父类的注解(反射可见)
    }
}
 

@Repeatable

  1. 作用:允许同一位置多次使用同一个注解。
  2. 注意事项
    • JDK 1.8 引入
    • 需要定义一个容器注解存放多个实例
    • 编译器自动打包多次使用的注解到容器注解
    • 反射可通过 getAnnotationsByType 获取所有注解
  3. 源码定义
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    Class<? extends Annotation> value();
}
 
  1. 使用示例
import java.lang.annotation.*;
 
@Repeatable(Roles.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Role {
    String value();
}
 
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Roles {
    Role[] value();
}
 
@Role("admin")
@Role("user")
class Demo {}
 
public class Test {
    public static void main(String[] args) {
        Role[] roles = Demo.class.getAnnotationsByType(Role.class);
        for (Role r : roles) System.out.println(r.value());
        // 输出:admin  user
    }
}
 

自定义注解

注解的定义语法

  1. 作用:自定义注解允许开发者为类、方法、字段等程序元素添加自定义元信息,从而供编译器、工具或框架处理。
  2. 关键字:使用 @interface 定义注解,本质是一个特殊接口,继承自 java.lang.annotation.Annotation
  3. 语法模板
[修饰符] @interface 注解名称 {
    // 属性列表
}
  1. 示例
import java.lang.annotation.*;
 
// 定义一个简单注解
public @interface MyAnnotation {}
 
// 使用自定义注解
@MyAnnotation
public class Person {
    @MyAnnotation
    private String name;
}
 

注解的属性与默认值

  1. 作用:在自定义注解中,可以通过定义方法来声明注解的属性(也叫元素),用于存储附加信息。
  2. 注意事项
    • 注解的属性必须是有限的可支持类型:基本数据类型、StringClass、枚举类型、其他注解类型,或以上类型的一维数组。
    • 可以通过 default 指定默认值,如果未指定,则使用注解时必须为该属性赋值。
    • 注解属性不能有普通方法实现体。
    • 注解属性名不能重复。
  3. 示例
import java.lang.annotation.*;
 
// 定义一个注解,包含属性及默认值
public @interface MyAnnotation {
    String name() default "Unknown"; // 默认值为 "Unknown"
    int age() default 18;            // 默认值为 18
}
 
// 使用示例
@MyAnnotation
public class Person {
    @MyAnnotation(name = "Alice", age = 25)
    private String name;
 
    @MyAnnotation // 使用默认值
    private int id;
}
 

value 属性的简化写法

  1. 作用:如果注解中只有一个属性,并且属性名为 value,在使用注解时可以省略 value=,让代码更简洁。
  2. 注意事项
    • 仅适用于单个 value 属性的情况,如果属性名不是 value,或者注解中有多个属性,则无法省略。
    • 如果有多个属性,即使其中一个是 value,也不能省略其他属性。
  3. 示例
import java.lang.annotation.*;
 
// 定义一个只含 value 属性的注解
public @interface MyAnnotation {
    String value();
}
 
// 使用简化写法
@MyAnnotation("Hello")   // 等价于 @MyAnnotation(value = "Hello")
 
// 如果有多个属性,必须显式赋值
public @interface MultiAttributeAnnotation {
    String value();
    int age();
}
 
// 使用示例
@MultiAttributeAnnotation(value = "Hi", age = 20) // 必须写 value=
 

自定义注解使用示例

  1. 作用:展示如何定义并使用自定义注解,包括类、方法和字段的应用。
  2. 注意事项
    • Java 从 JDK 1.5 引入注解机制,自定义注解成为开发中元数据的重要手段。
    • 自定义注解的属性可有默认值,也可在使用时显式赋值。
    • 自定义注解可以为程序元素添加额外信息,被编译器、框架或运行时反射读取处理。
    • 注解可以加上元注解(如 @Retention@Target)来控制生命周期和适用范围。
  3. 示例

示例 1:简单注解

import java.lang.annotation.*;
 
// 定义自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
public @interface MyAnnotation {
    String value() default "默认值";
}
 
@MyAnnotation("应用于类")
public class Person {
 
    @MyAnnotation("应用于字段")
    private String name;
 
    @MyAnnotation("应用于方法")
    public void sayHello() {
        System.out.println("Hello");
    }
}
 

示例 2:带多个属性的注解

import java.lang.annotation.*;
 
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Info {
    String author();
    int version() default 1;
}
 
@Info(author = "sun", version = 2)
public class DemoClass {}
 

package 注解

  1. 作用:用于为 Java 包(package)声明注解,提供该包的元数据。
  2. 注意事项
    • 从 JDK 5 引入,允许开发者在 package-info.java 文件中对包进行注解。
    • 常用于标记包级别信息,如作者、版本、文档说明或自定义元数据。
    • package 注解必须放在 package-info.java 文件中,文件必须存在于包目录下。
    • 该文件只能包含 package 注解和 package 声明,不能有其他类或接口。
    • 注解作用域为整个包,对包下所有类可统一说明或处理。
  3. 示例:
// 定义自定义包注解
import java.lang.annotation.*;
 
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PACKAGE)
public @interface PackageInfo {
    String author();
    String version() default "1.0";
}
 
// 文件:com/example/package-info.java
@PackageInfo(author = "sun", version = "2.0")
package com.example;
 
import com.example.PackageInfo;
 

注解在反射中的使用

反射机制允许在运行时获取类、方法、字段等程序元素的注解信息,从而实现动态处理逻辑,如自动注入、权限校验、配置加载等。

获取类的注解

  1. 作用:获取类上声明的注解实例。
  2. 方法
    • Class<?>.getAnnotation(Class<A> annotationClass):返回指定类型的注解,如果不存在返回 null
    • Class<?>.getAnnotations():返回类上所有注解的数组。
    • Class<?>.getDeclaredAnnotations():返回类上所有直接声明的注解,不包含继承的。
  3. 示例
import java.lang.annotation.*;
import java.lang.reflect.*;
 
// 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Info {
    String author();
    String version() default "1.0";
}
 
// 使用注解
@Info(author = "sun", version = "2.0")
class MyClass {}
 
// 反射读取类注解
public class Demo {
    public static void main(String[] args) {
        Class<MyClass> clazz = MyClass.class;
 
        // 获取单个注解
        Info info = clazz.getAnnotation(Info.class);
        if (info != null) {
            System.out.println("Author: " + info.author());
            System.out.println("Version: " + info.version());
        }
 
        // 获取所有注解
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation a : annotations) {
            System.out.println(a);
        }
    }
}
 

获取属性的注解

获取方法的注解

注解处理器(APT)

最佳实践

面试必备

相关文章

评论区