首页 > 编程笔记 > Java笔记 阅读:66

Java注解用法详解(附带实例)

Java 中提供了 Annotation 注解功能,该功能可用于类、构造方法、成员变量、成员方法、参数等的声明中。该功能并不影响程序的运行,但是会对编译器警告等辅助工具产生影响。

Java定义注解类型

在定义 Annotation 类型时,也需要用到用来定义接口的 interface 关键字,但需要在 interface 关键字前加一个“@”符号,即定义 Annotation 类型的关键字 为@interface,这个关键字的隐含意思是继承了 java.lang.annotation.Annotation 接口。

例如,下面的代码定义一个 Annotation 类型:
public @interface NoMemberAnnotation {
}

上面定义的 Annotation 类型 @NoMemberAnnotation 未包含任何成员,这样的 Annotation 类型被称为 marker annotation。

下面的代码定义一个只包含一个成员的 Annotation 类型:
public @interface OneMemberAnnotation {
    String 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 中的枚举常量用来设置 @Target,如下表所示。

表:枚举类 ElementType 中的枚举常量
枚举常量 说明
ANNOTATION_TYPE 表示用于Annotation类型
TYPE 表示用于类、接口和枚举,以及Annotation类型
CONSTRUCTOR 表示用于构造方法
FIELD 表示用于成员变量和枚举常量
METHOD 表示用于方法
PARAMETER 表示用于参数
LOCAL_VARIABLE 表示用于局部变量
PACKAGE 表示用于包


通过 Annotation 类型 @Retention 可以设置 Annotation 的有效范围。枚举类 RetentionPolicy 中的枚举常量用来设置 @Retention,如下表所示。

表:枚举类RetentionPolicy中的枚举常量
枚举常量 说明
SOURCE 表示不编译Annotation到类文件中,有效范围最小
CLASS 表示编译Annotation到类文件中,但是在运行时不加载Annotation到JVM中
RUNTIME 表示在运行时加载Annotation到JVM中,有效范围最大

如果未设置 @Retention,那么 Annotation 的有效范围为枚举常量 CLASS 表示的范围。

【实例 1】创建自定义的注解。首先定义一个用来注释构造方法的 Annotation 类型 @Constructor_Annotation,有效范围为在运行时加载 Annotation 到 JVM 中。完整代码如下:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.CONSTRUCTOR) //用于构造方法
@Retention(RetentionPolicy.RUNTIME) //在运行时加载Annotation到JVM中
public @interface Constructor_Annotation {
    String value() default "默认构造方法";
}

然后定义一个用来注释字段、方法和参数的 Annotation 类型 @Field_Method_Parameter_Annotation,有效范围为在运行时加载 Annotation 到 JVM 中。完整代码如下:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//用于字段、方法和参数
@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 = "设置编号")
    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;
    }
}

Java访问注解信息

如果在定义 Annotation 类型时将 @Retention 设置为 RetentionPolicy.RUNTIME,那么在运行程序时通过反射就可以获取到相关的 Annotation 信息,如获取构造方法、字段和方法的 Annotation 信息。

Constructor 类、Field 类和 Method 类均继承了 AccessibleObject 类,在AccessibleObject 中定义了 3 个关于 Annotation 的方法:
在 Constructor 类和 Method 类中还定义了方法 getParameterAnnotations(),用来获得为所有参数添加的 Annotation,将以 Annotation 类型的二维数组返回,在数组中的顺序与声明的顺序相同。如果没有参数,则返回一个长度为 0 的数组;如果存在未添加 Annotation 的参数,则将用一个长度为 0 的嵌套数组占位。

【实例 2】访问注释中的信息。本例将对实例 1 进行扩展,实现在程序运行时通过反射访问 Record 类中的 Annotation 信息。首先编写访问构造方法及其包含参数的 Annotation 信息的代码。关键代码如下:
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)
            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.println(" " + 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.println(" " + 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)
            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.println(" " + 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

相关文章