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所示。

image 2024 03 05 18 23 42 413
Figure 1. 表16.6 枚举类ElementType中的枚举常量

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

image 2024 03 05 18 24 42 215
Figure 2. 表16.7 枚举类RetentionPolicy中的枚举常量

【例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”。