Java 注解(Annotations)

更新于 2025-12-27

Jakob Jenkov 2021-07-28

Java 注解用于为你的 Java 代码提供元数据(meta data)。作为元数据,Java 注解本身不会直接影响代码的执行逻辑,尽管某些类型的注解确实可以被用于此目的。

Java 注解是从 Java 5 开始引入的。本文内容基于 Java 8、Java 9 及更高版本中的注解特性编写。据我所知,后续 Java 版本中注解没有发生重大变化,因此本文对 Java 8、9、10 和 11 的开发者同样适用。

Java 注解的用途

Java 注解通常用于以下几种目的:

  • 编译器指令
  • 构建时指令
  • 运行时指令

Java 内置了 3 个注解,可用于向 Java 编译器传递指令。这些注解将在下文详细说明。

构建阶段(build-time),注解也可发挥作用。构建过程包括生成源代码、编译源码、生成 XML 文件(例如部署描述符)、将编译后的代码和文件打包成 JAR 等。构建通常由自动化构建工具(如 Apache Ant 或 Apache Maven)完成。这些工具可以扫描 Java 代码中的特定注解,并据此生成源代码或其他文件。


通过 Java 反射访问注解

通常情况下,Java 注解在代码编译后不会保留在字节码中。但你可以自定义注解,并指定其在运行时保留。这类注解可以通过 Java 反射(Reflection) 访问,从而为程序或第三方 API 提供运行时指令。

关于如何通过反射访问注解,我在 《Java 反射与注解》 教程中有详细说明。


注解基础

最简单的 Java 注解形式如下:

@Entity

@ 符号告诉编译器这是一个注解,其后的名称(如 Entity)是注解的类型名。


注解元素(Annotation Elements)

Java 注解可以包含元素(elements),用于设置值。元素类似于属性或参数。

示例:带一个元素的注解

@Entity(tableName = "vehicles")

此注解包含一个名为 tableName 的元素,其值为 "vehicles"。元素写在注解名称后的括号内。若注解无元素,则可省略括号。

注解也可以包含多个元素:

@Entity(tableName = "vehicles", primaryKey = "id")

如果注解只有一个元素,按惯例应将其命名为 value

@InsertNew(value = "yes")

当唯一元素名为 value 时,可以省略元素名,直接写值:

@InsertNew("yes")

注解的位置(Placement)

Java 注解可以放在以下位置:

  • 类(class)
  • 接口(interface)
  • 方法(method)
  • 方法参数(method parameter)
  • 字段(field)
  • 局部变量(local variable)

示例:类上的注解

@Entity
public class Vehicle {}

更完整的示例:

@Entity
public class Vehicle {
    @Persistent
    protected String vehicleName = null;

    @Getter
    public String getVehicleName() {
        return this.vehicleName;
    }

    public void setVehicleName(@Optional String vehicleName) {
        this.vehicleName = vehicleName;
    }

    public List<String> addVehicleNameToList(List<String> names) {
        @Optional
        List<String> localNames = names;
        if (localNames == null) {
            localNames = new ArrayList<>();
        }
        localNames.add(getVehicleName());
        return localNames;
    }
}

注意:上述 @Entity@Persistent 等均为示例注解,在标准 Java 中并无实际含义。


内置的 Java 注解

Java 提供了几个内置注解,用于向编译器传递指令:

  • @Deprecated
  • @Override
  • @SuppressWarnings
  • @Contended

@Deprecated

用于标记类、方法或字段为“已弃用”,表示不应再使用。若代码中使用了被 @Deprecated 标记的元素,编译器会发出警告。

示例:

@Deprecated
public class MyComponent {}

也可用于方法或字段。

建议:同时使用 Javadoc 的 @deprecated 标签,说明弃用原因及替代方案:

/**
 * @deprecated Use MyNewComponent instead.
 */
@Deprecated
public class MyComponent {}

@Override

用于标注重写父类方法的方法。如果该方法在父类中不存在,编译器会报错。

虽然不加 @Override 也能成功重写,但加上它有助于防止因父类方法签名变更而导致的意外错误。

示例:

public class MySuperClass {
    public void doTheThing() {
        System.out.println("Do the thing");
    }
}

public class MySubClass extends MySuperClass {
    @Override
    public void doTheThing() {
        System.out.println("Do it differently");
    }
}

若父类的 doTheThing() 方法被重命名或签名改变,子类中的 @Override 将触发编译错误,提醒开发者修正。

@SuppressWarnings

用于抑制编译器警告。例如,调用已弃用方法或进行不安全的类型转换时,编译器会警告。使用此注解可关闭特定警告。

示例:

@SuppressWarnings
public void methodWithWarning() {}

实际使用中通常会指定要抑制的警告类型,如 @SuppressWarnings("deprecation")

@Contended

全名为 @jdk.internal.vm.annotation.Contended,用于避免伪共享(False Sharing)——一种并发性能问题。

更多详情请参阅:Java 伪共享


创建你自己的 Java 注解

你可以定义自定义注解。注解定义在一个独立的文件中,语法类似接口。

自定义注解示例

@interface MyAnnotation {
    String value();
    String name();
    int age();
    String[] newNames();
}
  • 使用 @interface 声明注解。
  • 每个元素的定义类似接口中的方法,有返回类型和名称。
  • 支持所有基本类型、字符串、枚举、注解类型及它们的数组。
  • 不支持复杂对象(如自定义类)作为元素类型

使用方式:

@MyAnnotation(
    value = "123",
    name = "Jakob",
    age = 37,
    newNames = {"Jenkov", "Peterson"}
)
public class MyClass {}

元素默认值

可为元素指定默认值,使其变为可选:

@interface MyAnnotation {
    String value() default "";
    String name();
    int age();
    String[] newNames();
}

使用时可省略 value

@MyAnnotation(
    name = "Jakob",
    age = 37,
    newNames = {"Jenkov", "Peterson"}
)
public class MyClass {}

元注解(Meta-Annotations)

用于修饰其他注解的注解称为元注解

@Retention

指定注解的保留策略(Retention Policy)

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
    String value() default "";
}

保留策略选项:

  • RetentionPolicy.SOURCE:仅在源码中存在,编译后丢弃(适用于构建工具扫描)。
  • RetentionPolicy.CLASS:保留在 .class 文件中,但运行时不可见(默认策略)。
  • RetentionPolicy.RUNTIME:保留在 .class 文件中,且运行时可通过反射访问

@Target

指定注解可应用的程序元素类型

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
public @interface MyAnnotation {
    String value();
}

ElementType 可选值包括:

  • ANNOTATION_TYPE:只能用于注解定义(如 @Target 本身)
  • CONSTRUCTOR
  • FIELD
  • LOCAL_VARIABLE
  • METHOD
  • PACKAGE
  • PARAMETER
  • TYPE:类、接口、枚举、注解
  • TYPE_PARAMETER(Java 8+)
  • TYPE_USE(Java 8+)

@Inherited

表示注解可被子类继承

@Inherited
public @interface MyAnnotation {}

@MyAnnotation
public class MySuperClass { ... }

public class MySubClass extends MySuperClass { ... }

此时 MySubClass 也“拥有” @MyAnnotation 注解。

@Documented

指示 JavaDoc 工具在生成文档时包含该注解:

import java.lang.annotation.Documented;

@Documented
public @interface MyAnnotation {}

这样,使用 @MyAnnotation 的类在 JavaDoc 中会显示该注解。