前言
本文介绍 Java 的注解 Annotations.
正文
什么是注解
注解是一种元数据,提供关于程序的数据,但这些数据并非程序本身的一部分。注解对其所注解代码的运行也没有直接影响。
注解的用途包括以下:
- 为编译器提供信息——编译器可以使用注解来检测错误或者抑制警告。
- 编译时和部署时处理——软件工具可以处理注解信息来生成代码、XML 文件等。
- 运行时处理——某些注解在运行时可供检查
注解的格式
注解一般长成这样:
使用 @ 符号告诉编译器表明它后面跟的是一个注解。如常见的 @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 { ... }
@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 版本开始,注解也可以应用于类型的使用:
预定义注解
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(); names.add("Alice"); names.add("Bob");
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
| @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Author { String name(); }
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Authors { Author[] value(); }
@Repeatable(Authors.class) @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Author { String name(); }
@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 date(); int version() default 1; String[] tags() default {}; Class<?> handler(); }
|
应用注解
一旦定义了注解,就可以像使用内置注解一样使用它:
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..."); }
}
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 { Class<MyService> myServiceClass = MyService.class;
Method processDataMethod = myServiceClass.getMethod("processData");
if (processDataMethod.isAnnotationPresent(MyMethodInfo.class)) { 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),而这些元数据不会直接影响程序本身的运行逻辑,但可以被工具或框架在编译、部署或运行时读取和处理。