前言
本文介绍 Java
的注解 Annotations
.
正文
什么是注解
注解是一种元数据,提供关于程序的数据,但这些数据并非程序本身的一部分。注解对其所注解代码的运行也没有直接影响。
注解的用途包括以下:
- 为编译器提供信息——编译器可以使用注解来检测错误或者抑制警告。
- 编译时和部署时处理——软件工具可以处理注解信息来生成代码、XML 文件等。
- 运行时处理——某些注解在运行时可供检查
注解的格式
注解一般长成这样:
1 |
使用 @
符号告诉编译器表明它后面跟的是一个注解。如常见的 @Override
:
1 |
|
注解可以包含元素,这些元素可以有名称或没有名称,并且都应该有对应的值。如果只有一个名为 value
的元素,那么可以省略该名称。如果注解没有元素,那么可以省略括号。
1 | // 有名称的元素 |
在同一个声明上可以使用多个注解,如果注解的类型相同,称其为重复注解:
1 | // 同一声明,多个注解 |
注解的应用场景
注解可以应用于声明:包括类、字段、方法以及其他程序元素的声明。按照惯例,当注解用于声明时,每个注解通常会单独占一行。
从 Java SE 8 版本开始,注解也可以应用于类型的使用:
类实例创建表达式:
new @Interned MyObject();
类型转换:
myString = (@NonNull String) str;
implements
子句:1
2class UnmodifiableList<T> implements
List< T> { ... }抛出异常声明:
1
2void monitorTemperature() throws
TemperatureException { ... }
预定义注解
Java 提供了一些预定义的标准注解,它们是 Java 语言规范的一部分,用于编译器指示、代码分析和运行时处理等常见任务。
常用的预定义注解有:@Override
, @Deprecated
, @SuppressWarnings
.
@Override
这个注解用于标记一个方法是重写(override)父类或接口中的方法。
- 用途:帮助编译器检查,如果你没有正确地重写方法(例如,方法签名不匹配),编译器会报错。这有助于防止潜在的运行时错误。
- 保留策略:
SOURCE
(只在源代码中保留,编译后丢弃)。
@Deprecated
这个注解用于标记一个程序元素(类、方法、字段等)已经过时,不推荐使用。
- 用途:当代码中使用了被
@Deprecated
标记的元素时,编译器会发出警告。这有助于开发人员了解哪些 API 应该被替换为更新、更安全或更有效的方式。 - 保留策略:
RUNTIME
(运行时保留)。
1 | // 标记整个类已过时 |
@SuppressWarnings
这个注解用于抑制编译器产生的特定警告。
- 用途:当你确定某个警告是“误报”或你知道自己在做什么,并且不希望看到编译器的警告信息时,可以使用它。你可以指定要抑制的警告类型,例如 “unchecked”(未检查的类型转换)、”rawtypes”(使用原始类型)等。
- 保留策略:
SOURCE
。
1 | import java.util.ArrayList; |
元注解
元注解(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 | // 1. 定义可重复注解 |
自定义注解
定义注解
注解本质上是一个特殊的接口,使用 @interface
关键字来定义。
1 | public MyCustomAnnotation { |
使用元注解
使用 @Target
, @Retention
, @Documented
, @Inherited
, @Repeatable
来定义注解的作用范围和生命周期。
定义注解元素
注解可以包含元素,这些元素看起来像方法声明,但它们没有方法体,并且可以有默认值。
- 类型: 注解元素的返回类型只能是以下几种:
- 基本数据类型 (primitives):
byte
,short
,int
,long
,float
,double
,boolean
,char
String
Class
enum
(枚举类型)- 注解类型 (
Annotation
) - 以上类型的数组
- 基本数据类型 (primitives):
- 默认值: 可以使用
default
关键字为元素指定默认值。 value
元素: 如果注解只有一个元素,并且该元素的名称是value
,那么在使用注解时,可以省略value =
。
示例:
1 | import java.lang.annotation.ElementType; |
应用注解
一旦定义了注解,就可以像使用内置注解一样使用它:
1 | public class MyService { |
处理注解
要让自定义注解发挥作用,需要通过反射 (Reflection) 在运行时读取并处理它们。
1 | import java.lang.reflect.Method; |
后记
Java 注解的核心思想是向程序的各个部分添加元数据(metadata),而这些元数据不会直接影响程序本身的运行逻辑,但可以被工具或框架在编译、部署或运行时读取和处理。