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本身)CONSTRUCTORFIELDLOCAL_VARIABLEMETHODPACKAGEPARAMETERTYPE:类、接口、枚举、注解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 中会显示该注解。