抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前言

本文介绍 Java 的注解 Annotations.

正文

什么是注解

注解是一种元数据,提供关于程序的数据,但这些数据并非程序本身的一部分。注解对其所注解代码的运行也没有直接影响。

注解的用途包括以下:

  • 为编译器提供信息——编译器可以使用注解来检测错误或者抑制警告。
  • 编译时和部署时处理——软件工具可以处理注解信息来生成代码、XML 文件等。
  • 运行时处理——某些注解在运行时可供检查

注解的格式

注解一般长成这样:

1
@Entity

使用 @ 符号告诉编译器表明它后面跟的是一个注解。如常见的 @Override

1
2
@Override
void mySuperMethod() { ... }

注解可以包含元素,这些元素可以有名称没有名称,并且都应该有对应的值。如果只有一个名为 value 的元素,那么可以省略该名称。如果注解没有元素,那么可以省略括号。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 有名称的元素
@Author(
name = "Benjamin Franklin",
date = "3/27/2003"
)
class MyClass { ... }

// 只有一个值 value,省略名称
@SuppressWarnings(value = "unchecked")
void myMethod() { ... }

@SuppressWarnings("unchecked")
void myMethod() { ... }

在同一个声明上可以使用多个注解,如果注解的类型相同,称其为重复注解

1
2
3
4
5
6
7
8
9
// 同一声明,多个注解
@Author(name = "Jane Doe")
@EBook
class MyClass { ... }

// 重复注解
@Author(name = "Jane Doe")
@Author(name = "John Smith")
class MyClass { ... }

注解的应用场景

注解可以应用于声明:包括类、字段、方法以及其他程序元素的声明。按照惯例,当注解用于声明时,每个注解通常会单独占一行。

从 Java SE 8 版本开始,注解也可以应用于类型的使用

  • 类实例创建表达式:new @Interned MyObject();

  • 类型转换:myString = (@NonNull String) str;

  • implements 子句:

    1
    2
    class UnmodifiableList<T> implements
    @Readonly List<@Readonly T> { ... }
  • 抛出异常声明:

    1
    2
    void monitorTemperature() throws
    @Critical TemperatureException { ... }

预定义注解

Java 提供了一些预定义的标准注解,它们是 Java 语言规范的一部分,用于编译器指示、代码分析和运行时处理等常见任务。

常用的预定义注解有:@Override, @Deprecated, @SuppressWarnings.

@Override

这个注解用于标记一个方法是重写(override)父类或接口中的方法

  • 用途:帮助编译器检查,如果你没有正确地重写方法(例如,方法签名不匹配),编译器会报错。这有助于防止潜在的运行时错误。
  • 保留策略SOURCE (只在源代码中保留,编译后丢弃)。

@Deprecated

这个注解用于标记一个程序元素(类、方法、字段等)已经过时,不推荐使用

  • 用途:当代码中使用了被 @Deprecated 标记的元素时,编译器会发出警告。这有助于开发人员了解哪些 API 应该被替换为更新、更安全或更有效的方式。
  • 保留策略RUNTIME (运行时保留)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Deprecated // 标记整个类已过时
class OldUtility {
@Deprecated // 标记方法已过时
public void oldMethod() {
System.out.println("This method is old and deprecated.");
}
}

public class Main {
public static void main(String[] args) {
OldUtility util = new OldUtility();
util.oldMethod(); // 编译器会给出警告
}
}

@SuppressWarnings

这个注解用于抑制编译器产生的特定警告

  • 用途:当你确定某个警告是“误报”或你知道自己在做什么,并且不希望看到编译器的警告信息时,可以使用它。你可以指定要抑制的警告类型,例如 “unchecked”(未检查的类型转换)、”rawtypes”(使用原始类型)等。
  • 保留策略SOURCE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.ArrayList;
import java.util.List;

public class WarningExample {
@SuppressWarnings("unchecked") // 抑制未检查类型转换的警告
public static void main(String[] args) {
List names = new ArrayList(); // 未指定泛型,会产生 "rawtypes" 警告
names.add("Alice");
names.add("Bob");

// 这里的强制类型转换会产生 "unchecked" 警告
// 如果没有 @SuppressWarnings("unchecked"),编译器会警告
String name = (String) names.get(0);
System.out.println(name);
}

@SuppressWarnings({"rawtypes", "deprecation"}) // 抑制多种警告
public void anotherMethod() {
List rawList = new ArrayList();
new OldUtility().oldMethod(); // 使用了已过时的方法
}
}

元注解

元注解meta-annotations),是用于注解其他注解的注解。它们定义了自定义注解的行为作用域

@Retention

指定了被其标记的注解将如何被存储,即注解在何时可用。

  • RetentionPolicy.SOURCE – 被标记的注解仅保留在源代码级别,并被编译器忽略。这意味着它们在编译后不会包含在字节码文件中。
  • RetentionPolicy.CLASS – 被标记的注解在编译时被编译器保留在类文件中,但会被 Java 虚拟机(JVM)忽略。这意味着你无法在运行时通过反射访问它们。
  • RetentionPolicy.RUNTIME – 被标记的注解由 JVM 保留,因此它们可以在运行时环境中使用。这是最常用的策略,因为大多数需要被程序读取和处理的注解都需要在运行时可用。

@Documented

如果一个注解被 @Documented 修饰,那么当使用 javadoc 工具生成文档时,这个注解也会被包含在文档中。

@Target

定义注解可以应用于哪些程序元素。

  • ElementType.TYPE: 类、接口(包括注解类型)、枚举
  • ElementType.FIELD: 字段(包括枚举常量)
  • ElementType.METHOD: 方法
  • ElementType.PARAMETER: 方法参数
  • ElementType.CONSTRUCTOR: 构造函数
  • ElementType.LOCAL_VARIABLE: 局部变量
  • ElementType.ANNOTATION_TYPE: 注解类型
  • ElementType.PACKAGE: 包

如果你希望注解可以用于多种类型,可以使用数组:@Target({ElementType.TYPE, ElementType.METHOD})

@Inherited

如果一个注解被 @Inherited 修饰,那么当一个类被这个注解标记时,它的子类也会自动继承这个注解。需要注意的是,@Inherited 只对类有效,对方法和字段无效。

@Repeatable

允许同一个注解在同一个声明上重复使用多次。在使用 @Repeatable 时,需要定义一个“容器注解”来包裹重复的注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 1. 定义可重复注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Author {
String name();
}

// 2. 定义容器注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Authors {
Author[] value(); // 数组类型,存放多个 Author 注解
}

// 3. 在 Author 注解上使用 @Repeatable
@Repeatable(Authors.class) // 指定容器注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Author {
String name();
}

// 4. 用例
@Author(name = "Jane Doe")
@Author(name = "John Smith")
class MyClass { ... }

自定义注解

定义注解

注解本质上是一个特殊的接口,使用 @interface 关键字来定义。

1
2
3
public @interface MyCustomAnnotation {
// 元素(可选)
}

使用元注解

使用 @Target, @Retention, @Documented, @Inherited, @Repeatable 来定义注解的作用范围和生命周期。

定义注解元素

注解可以包含元素,这些元素看起来像方法声明,但它们没有方法体,并且可以有默认值。

  • 类型: 注解元素的返回类型只能是以下几种:
    • 基本数据类型 (primitives): byte, short, int, long, float, double, boolean, char
    • String
    • Class
    • enum (枚举类型)
    • 注解类型 (Annotation)
    • 以上类型的数组
  • 默认值: 可以使用 default 关键字为元素指定默认值。
  • value 元素: 如果注解只有一个元素,并且该元素的名称是 value,那么在使用注解时,可以省略 value =

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMethodInfo {
String author() default "Unknown"; // 带有默认值的 String 类型元素
String date(); // 没有默认值的 String 类型元素,使用时必须赋值
int version() default 1; // 带有默认值的 int 类型元素
String[] tags() default {}; // String 数组类型,默认空数组
Class<?> handler(); // Class 类型元素
}

应用注解

一旦定义了注解,就可以像使用内置注解一样使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyService {

@MyMethodInfo(author = "Alice", date = "2025-06-16", handler = MyHandler.class)
public void processData() {
System.out.println("Processing data...");
}

@MyMethodInfo(date = "2025-06-15", handler = DefaultHandler.class, version = 2)
public void anotherMethod() {
System.out.println("Another method...");
}

// 如果只有一个 value 元素,且注解名称为 value
// @MySingleValueAnnotation("This is a single value")
// public void doSomething() {}
}

class MyHandler {}
class DefaultHandler {}

处理注解

要让自定义注解发挥作用,需要通过反射 (Reflection)运行时读取并处理它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.lang.reflect.Method;

public class AnnotationProcessor {
public static void main(String[] args) throws NoSuchMethodException {
// 获取 MyService 类的 Class 对象
Class<MyService> myServiceClass = MyService.class;

// 获取 processData 方法的 Method 对象
Method processDataMethod = myServiceClass.getMethod("processData");

// 检查该方法上是否存在 MyMethodInfo 注解
if (processDataMethod.isAnnotationPresent(MyMethodInfo.class)) {
// 获取 MyMethodInfo 注解实例
MyMethodInfo annotation = processDataMethod.getAnnotation(MyMethodInfo.class);

// 访问注解的元素
System.out.println("Method: " + processDataMethod.getName());
System.out.println(" Author: " + annotation.author());
System.out.println(" Date: " + annotation.date());
System.out.println(" Version: " + annotation.version());
System.out.println(" Handler: " + annotation.handler().getName());
System.out.println(" Tags: " + String.join(", ", annotation.tags()));
}

System.out.println("\n--- Another Method ---");
Method anotherMethod = myServiceClass.getMethod("anotherMethod");
if (anotherMethod.isAnnotationPresent(MyMethodInfo.class)) {
MyMethodInfo annotation = anotherMethod.getAnnotation(MyMethodInfo.class);
System.out.println("Method: " + anotherMethod.getName());
System.out.println(" Author: " + annotation.author()); // 使用默认值
System.out.println(" Date: " + annotation.date());
System.out.println(" Version: " + annotation.version());
System.out.println(" Handler: " + annotation.handler().getName());
System.out.println(" Tags: " + String.join(", ", annotation.tags())); // 使用默认空数组
}
}
}

后记

Java 注解的核心思想是向程序的各个部分添加元数据(metadata),而这些元数据不会直接影响程序本身的运行逻辑,但可以被工具或框架在编译、部署或运行时读取和处理。

评论