Annotation注解功能
Java 中提供了 Annotation 注解功能,该功能可用于类、构造方法、成员变量、成员方法、参数等的声明中。该功能并不影响程序的运行,但是会对编译器警告等辅助工具产生影响。本节将讲解 Annotation 功能的使用方法。
定义 Annotation 类型
在定义 Annotation 类型时,也需要用到用来定义接口的 interface 关键字,但需要在 interface 关键字前加一个 “@” 符号,即定义 Annotation 类型的关键字为 @interface,这个关键字的隐含意思是继承了 java.lang.annotation.Annotation 接口。例如,下面的代码定义一个 Annotation 类型:
public @interface NoMemberAnnotation {
}
上面定义的 Annotation 类型 @NoMemberAnnotation 未包含任何成员,这样的 Annotation 类型被称为 marker annotation。下面的代码定义一个只包含一个成员的 Annotation 类型:
public @interface OneMemberAnnotatioin {
String value();
}
-
String:成员类型。可用的成员类型有 String、Class、primitive、enumerated 和 annotation,以及所列类型的数组。
-
value:成员名称。如果在所定义的 Annotation 类型中只包含一个成员,通常将成员名称命名为 value。
下面的代码定义一个包含多个成员的 Annotation 类型:
public @interface MoreMemberAnnotation {
String describe();
Class type();
}
在为 Annotation 类型定义成员时,也可以为成员设置默认值。例如,下面的代码在定义 Annotation 类型时为成员设置默认值:
public @interface DefaultValueAnnotation {
String describe() default "<默认值>";
Class type() default void.class;
}
在定义 Annotation 类型时,还可以通过 Annotation 类型 @Target 来设置 Annotation 类型适用的程序元素种类。如果未设置 @Target,则表示适用于所有程序元素。枚举类 ElementType 中的枚举常量用来设置 @Targer,如表16.6所示。

通过 Annotation 类型 @Retention 可以设置 Annotation 的有效范围。枚举类 RetentionPolicy 中的枚举常量用来设置 @Retention,如表16.7所示。如果未设置 @Retention,那么 Annotation 的有效范围为枚举常量 CLASS 表示的范围。

【例16.4】创建自定义的注解(实例位置:资源包\TM\sl\16\4)
首先定义一个用来注释构造方法的 Annotation 类型 @Constructor_Annotation,有效范围为在运行时加载 Annotation 到 JVM 中。完整代码如下:
import java.lang.annotation.*;
@Target(ElementType.CONSTRUCTOR)
// 用于构造方法
@Retention(RetentionPolicy.RUNTIME)
// 在运行时加载Annotation到JVM中
public @interface Constructor_Annotation {
String value() default "默认构造方法"; // 定义一个具有默认值的String型成员
}
然后定义一个用来注释字段、方法和参数的 Annotation 类型 @Field_Method_Parameter_Annotation,有效范围为在运行时加载 Annotation 到 JVM 中。完整代码如下:
import java.lang.annotation.*;
// 用于字段、方法和参数
@Target( { ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
// 在运行时加载Annotation到JVM中
public @interface Field_Method_Parameter_Annotation {
String describe(); // 定义一个没有默认值的String型成员
Class type() default void.class; // 定义一个具有默认值的Class型成员
}
最后编写一个 Record 类,在该类中运用前面定义的 Annotation 类型 @Constructor_Annotation 和 @Field_Method_Parameter_Annotation 来对构造方法、字段、方法和参数进行注释。完整代码如下:
public class Record {
@Field_Method_Parameter_Annotation(describe = "编号", type = int.class)
// 注释字段
int id;
@Field_Method_Parameter_Annotation(describe = "姓名", type = String.class)
String name;
@Constructor_Annotation()
// 采用默认值注释构造方法
public Record() {
}
@Constructor_Annotation("立即初始化构造方法")
// 注释构造方法
public Record(@Field_Method_Parameter_Annotation(describe = "编号", type = int.class) int id,
@Field_Method_Parameter_Annotation(describe = "姓名", type = String.class) String name) {
this.id = id;
this.name = name;
}
@Field_Method_Parameter_Annotation(describe = "获得编号", type = int.class)
// 注释方法
public int getId() {
return id;
}
@Field_Method_Parameter_Annotation(describe = "设置编号")
// 成员type采用默认值注释方法
public void setId(
// 注释方法的参数
@Field_Method_Parameter_Annotation(describe = "编号", type = int.class) int id) {
this.id = id;
}
@Field_Method_Parameter_Annotation(describe = "获得姓名", type = String.class)
public String getName() {
return name;
}
@Field_Method_Parameter_Annotation(describe = "设置姓名")
public void setName(@Field_Method_Parameter_Annotation(describe = "姓名", type = String.class) String name) {
this.name = name;
}
}
访问 Annotation 信息
如果在定义 Annotation 类型时将 @Retention 设置为 RetentionPolicy.RUNTIME,那么在运行程序时通过反射就可以获取到相关的 Annotation 信息,如获取构造方法、字段和方法的 Annotation 信息。
Constructor类、Field类和 Method类均继承了 AccessibleObject 类,在 AccessibleObject 中定义了 3 个关于 Annotation 的方法。其中:方法isAnnotationPresent(Class<? extends Annotation> annotationClass) 用来查看是否添加了指定类型的 Annotation,如果是,则返回 true,否则返回 false;方法 getAnnotation(Class<T> annotationClass) 用来获得指定类型的 Annotation,如果存在,则返回相应的对象,否则返回 null;方法 getAnnotations() 用来获得所有的 Annotation,该方法将返回一个 Annotation 数组。
在 Constructor 类和 Method 类中还定义了方法 getParameterAnnotations(),用来获得为所有参数添加的 Annotation,将以 Annotation 类型的二维数组返回,在数组中的顺序与声明的顺序相同。如果没有参数,则返回一个长度为 0 的数组;如果存在未添加 Annotation 的参数,则将用一个长度为 0 的嵌套数组占位。
【例16.5】访问注释中的信息(实例位置:资源包\TM\sl\16\5)
本例将对16.2.1节中的例16.4进行扩展,实现在程序运行时通过反射访问 Record 类中的 Annotation 信息。首先编写访问构造方法及其包含参数的 Annotation 信息的代码。关键代码如下:
import java.lang.annotation.*;
import java.lang.reflect.*;
public class AnnotationTest {
public static void main(String[] args) {
Class recordC = null;
try {
recordC = Class.forName("Record");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("------ 构造方法的描述如下 ------");
Constructor[] declaredConstructors = recordC.getDeclaredConstructors(); // 获得所有构造方法
for (int i = 0; i < declaredConstructors.length; i++) {
Constructor constructor = declaredConstructors[i]; // 遍历构造方法
// 查看是否具有指定类型的注释
if (constructor.isAnnotationPresent(Constructor_Annotation.class)) {
// 获得指定类型的注释
Constructor_Annotation ca = (Constructor_Annotation) constructor
.getAnnotation(Constructor_Annotation.class);
System.out.println(ca.value()); // 获得注释信息
}
Annotation[][] parameterAnnotations = constructor.getParameterAnnotations(); // 获得参数的注释
for (int j = 0; j < parameterAnnotations.length; j++) {
// 获得指定参数注释的长度
int length = parameterAnnotations[j].length;
if (length == 0) // 如果长度为0则表示没有为该参数添加注释
System.out.println(" 未添加Annotation的参数");
else
for (int k = 0; k < length; k++) {
// 获得参数的注释
Field_Method_Parameter_Annotation pa = (Field_Method_Parameter_Annotation) parameterAnnotations[j][k];
System.out.print(" " + pa.describe()); // 获得参数描述
System.out.println(" " + pa.type()); // 获得参数类型
}
}
System.out.println();
}
然后编写访问字段的 Annotation 信息的代码。关键代码如下:
System.out.println("-------- 字段的描述如下 --------");
Field[] declaredFields = recordC.getDeclaredFields(); // 获得所有字段
for (int i = 0; i < declaredFields.length; i++) {
Field field = declaredFields[i]; // 遍历字段
// 查看是否具有指定类型的注释
if (field.isAnnotationPresent(Field_Method_Parameter_Annotation.class)) {
// 获得指定类型的注释
Field_Method_Parameter_Annotation fa = field.getAnnotation(Field_Method_Parameter_Annotation.class);
System.out.print(" " + fa.describe()); // 获得字段的描述
System.out.println(" " + fa.type()); // 获得字段的类型
}
}
最后编写访问方法及其包含参数的 Annotation 信息的代码。关键代码如下:
System.out.println("-------- 方法的描述如下 --------");
Method[] methods = recordC.getDeclaredMethods(); // 获得所有方法
for (int i = 0; i < methods.length; i++) {
Method method = methods[i]; // 遍历方法
// 查看是否具有指定类型的注释
if (method.isAnnotationPresent(Field_Method_Parameter_Annotation.class)) {
// 获得指定类型的注释
Field_Method_Parameter_Annotation ma = method.getAnnotation(Field_Method_Parameter_Annotation.class);
System.out.println(ma.describe()); // 获得方法的描述
System.out.println(ma.type()); // 获得方法的返回值类型
}
Annotation[][] parameterAnnotations = method.getParameterAnnotations(); // 获得参数的注释
for (int j = 0; j < parameterAnnotations.length; j++) {
int length = parameterAnnotations[j].length; // 获得指定参数注释的长度
if (length == 0) // 如果长度为0表示没有为该参数添加注释
System.out.println(" 未添加Annotation的参数");
else
for (int k = 0; k < length; k++) {
// 获得指定类型的注释
Field_Method_Parameter_Annotation pa = (Field_Method_Parameter_Annotation) parameterAnnotations[j][k];
System.out.print(" " + pa.describe()); // 获得参数的描述
System.out.println(" " + pa.type()); // 获得参数的类型
}
}
System.out.println();
}
运行结果如下:
------ 构造方法的描述如下 ------
默认构造方法
立即初始化构造方法
编号 int
姓名 class java.lang.String
-------- 字段的描述如下 --------
编号 int
姓名 class java.lang.String
-------- 方法的描述如下 --------
获得姓名
class java.lang.String
设置姓名
void
姓名 class java.lang.String
获得编号
int
设置编号
void
编号 int
编程训练(答案位置:资源包\TM\sl\16\编程训练)
【训练3】为用户登录方法创建注解 创建一个专门应用于方法上的注解,被该注解声明的方法为用户登录方法。
【训练4】为用户名和密码创建注解 在训练3的基础上创建两个参数注解,分别用于声明用户名和密码,并将用户名和密码设置为默认值“admin/admin”。