Java - 注解

159

元注解:

@Retention @Target @Document @Inherited

  • @Retention: 定义注解的保留策略

    @Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中, 在 class 字节码文件中不包含, 用于编译时, 典型为@Getter@Setter@Override

    @Retention(RetentionPolicy.CLASS) // 默认的保留策略, 注解会在 class 字节码文件中存在, 但运行时无法获得

    @Retention(RetentionPolicy.RUNTIME) // 注解会在 class 字节码文件中存在, 在运行时可以通过反射获取到

    生命周期大小排序为 SOURCE < CLASS < RUNTIME, 前者能使用的地方后者一定也能使用

    如果需要在运行时去动态获取注解信息,那只能用RUNTIME 注解

    如果要在编译时进行一些预处理操作, 比如生成一些辅助代码(如 ButterKnife), 就用 CLASS

    如果只是做一些检查性的操作, 比如 @Override@SuppressWarnings, 则可选用 SOURCE

  • @Target: 定义注解的使用范围

    @Target 注解用来指定一个注解的使用范围,即被 @Target 修饰的注解可以用在什么地方. @Target 注解有一个成员变量(value)用来设置适用目标, value java.lang.annotation.ElementType 枚举类型的数组, 下表为 ElementType 常用的枚举常量

名称说明
CONSTRUCTOR用于构造方法
FIELD用于成员变量(包括枚举常量)
LOCAL_VARIABLE用于局部变量
METHOD用于方法
PACKAGE用于包
PARAMETER用于类型参数(JDK 1.8新增)
TYPE用于类、接口(包括注解类型)或枚举
  • @Document: 用于生成 JavaDoc

    @Documented 是一个标记注解,没有成员变量. 用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档.默认情况下, JavaDoc 是不包括注解的, 但如果声明注解时指定了 @Documented, 就会被 JavaDoc 之类的工具处理, 所以注解类型信息就会被包括在生成的帮助文档中

  • @Inherited: 指定注解可被继承

    @Inherited 是一个标记注解,用来指定该注解可以被继承. 使用 @Inherited 注解的 Class 类, 表示这个注解可以被用于该 Class 类的子类. 就是说如果某个类使用了被 @Inherited 修饰的注解, 则其子类将自动具有该注解

Annotation 的工作原理:

JDK5.0 中提供了注解的功能, 允许开发者定义和使用自己的注解类型

该功能由一个定义注解类型的语法和描述一个注解声明的语法,
读取注解的 API, 一个使用注解修饰的 class 文件和一个注解处理工具组成

Annotation 并不直接影响代码的语义, 但是他可以被看做是程序的工具或者类库, 它会反过来对正在运行的程序语义有所影响

Annotation 可以从源文件 / class 文件或者在运行时通过反射机制多种方式被读取

@Override 注解:

表示一个方法声明打算重写超类中的另一个方法声明. 如果方法利用此注释类型进行注解但没有重写超类方法, 则编译器会生成一条错误消息

@Override注解表示子类要重写父类的对应方法

@Override是一个 Marker annotation, 用于标识的 Annotation, Annotation 名称本身表示了要给工具程序的信息

下面是一个使用@Override注解的例子:

class A {
    private String id;

    A(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return id;
    }
}

@Deprecated 注解:

@Deprecated 注释的程序元素, 不鼓励程序员使用这样的元素, 通常是因为它很危险或存在更好的选择

在使用不被赞成的程序元素或在不被赞成的代码中执行重写时, 编译器会发出警告

@Deprecated注解表示方法是不被建议使用的

@Deprecated 是一个Marker annotation

下面是一个使用@Deprecated注解的例子:

class A {
    private String id;

    A(String id) {
        this.id = id;
    }

    @Deprecated
    public void execute() {
        System.out.println(id);
    }

    public static void main(String[] args) {
        A a = new A("a123");
        a.execute();
    }
}

@SuppressWarnings 注解:

指示应该在注释元素(以及包含在该注释元素中的所有程序元素)
中取消显示指定的编译器警告

注意, 在给定元素中取消显示的警告集是所有包含元素中取消显示的警告的超集

例如, 如果注释一个类来取消显示某个警告, 同时注释一个方法来取消显示另一个警告, 那么将在此方法中同时取消显示这两个警告

根据风格不同, 程序员应该始终在最里层的嵌套元素上使用此注释, 在那里使用才有效

如果要在特定的方法中取消显示某个警告, 则应该注释该方法而不是注释它的类

@SuppressWarnings注解表示抑制警告

下面是一个使用@SuppressWarnings注解的例子:

class Test {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("abc");
    }
}

自定义注解:


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // ElementType.TYPE标明此注释可以标记的类型(类, 接口, 枚举, 方法), 可以以数组的方法添加多个
public @interface MyAnnotation {
    String value() default "Erzbir";

    int version();
}

使用自定义注解:

public class Test {

    @MyAnnotion(version = 1)
    public void test1() {

    }
}

注解中每一个成员都默认是 public, 且格式必须是: 数据类型 成员名称() default 值

如果不指定默认值, 使用时必须给一个默认值

通过反射拿到注解内容:

class Test {
    @MyAnnotation
    void test() {
        Annotation[] annotations = this.getClass().getAnnotation(MyAnnotation.class);
    }
}

使用 @interface 自定义注解时, 自动继承了java.lang.annotation.Annotation 接口, 由编译程序自动完成其他细节

在定义注解时, 不能继承其他的注解或接口

多变量使用枚举:

public @interface MyAnnotation {

    String value1() default "abc";

    MyEnum value2() default MyEnum.Sunny;
}

enum MyEnum {
    Sunny, Rainy
}

数组变量:

public @interface MyAnnotation {

    String[] value1() default "abc";
}

设置注解的作用范围:

@Retention

指示注释类型的注释要保留多久

如果注释类型声明中不存在 @Retention 注释, 则保留策略默认为 RetentionPolicy.CLASS只有元注释类型直接用于注释时, @Target 元注释才有效

如果元注释类型用作另一种注释类型的成员, 则无效

注解保留策略. 此枚举类型的常量描述保留注释的不同策略

它们与 @Retention 元注释类型一起使用, 以指定保留多长的注释.可用的注释保留策略:

  • CLASS
    编译器将把注释记录在类文件中, 但在运行时 VM 不需要保留注释
  • RUNTIME
    编译器将把注释记录在类文件中, 在运行时 VM 将保留注释,因此可以反射性地读取
  • SOURCE
    编译器要丢弃的注释

@Retention 注解可以在定义注解时为编译程序提供注解的保留策略

属于 CLASS 保留策略的注解有 @SuppressWarnings, 该注解信息不会存储于 .class 文件

在自定义注解中的使用例子:


@Retention(RetentionPolicy.CLASS)
public @interface MyAnnotation {
    String[] value1() default "abc";
}

使用反射读取RUNTIME保留策略的Annotation信息的例子:

java.lang.reflect
接口 AnnotatedElement
所有已知实现类:
AccessibleObject, Class, Constructor, Field, Method, Package

表示目前正在此 VM 中运行的程序的一个已注释元素

该接口允许反射性地读取注释

由此接口中的方法返回的所有注释都是不可变并且可序列化的

调用者可以修改已赋值数组枚举成员的访问器返回的数组; 这不会对其他调用者返回的数组产生任何影响

如果此接口中的方法返回的注释(直接或间接地)包含一个已赋值的 Class 成员, 该成员引用了一个在此 VM 中不可访问的类, 则试图通过在返回的注释上调用相关的类返回的方法来读取该类, 将导致一个 TypeNotPresentException

isAnnotationPresent:
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

如果指定类型的注释存在于此元素上, 则返回 true, 否则返回 false

此方法主要是为了便于访问标记注释而设计的

参数:

  • annotationClass - 对应于注释类型的 Class 对象

返回:

  • 如果指定注释类型的注释存在于此对象上, 则返回 true,否则返回 false

抛出:

  • NullPointerException - 如果给定的注释类为 null
getAnnotation:
T getAnnotation(Class annotationClass)

如果存在该元素的指定类型的注释, 则返回这些注释, 否则返回 null

参数:

  • annotationClass - 对应于注释类型的 Class 对象

返回:

  • 如果该元素的指定注释类型的注释存在于此对象上, 则返回这些注释, 否则返回 null

抛出:

  • NullPointerException - 如果给定的注释类为 null
getAnnotations:
Annotation[] getAnnotations()

返回此元素上存在的所有注释(如果此元素没有注释, 则返回长度为零的数组)

该方法的调用者可以随意修改返回的数组; 这不会对其他调用者返> 回的数组产生任何影响

返回: 此元素上存在的所有注释

getDeclaredAnnotation:
Annotation[] getDeclaredAnnotations() 

返回直接存在于此元素上的所有注释

与此接口中的其他方法不同,该方法将忽略继承的注释(如果没有注释直接存在于此元素上, 则返回长度为零的一个数组)

该方法的调用者可以随意修改返回的数组; 这不会对其他调用者返回的数组产生任何影响

返回:

  • 直接存在于此元素上的所有注释

使用反射读取 RUNTIME 保留策略的 Annotation 信息的例子:

自定义注解:


@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    String[] value1() default "abc";
}

读取注解中的信息:

class Test {
    public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        AnnotationTest2 annotationTest2 = new AnnotationTest2();
        //获取AnnotationTest2的Class实例
        Class<AnnotationTest2> c = AnnotationTest2.class;
//获取需要处理的方法Method实例
        Method method = c.getMethod("execute", new Class[]{});
        //判断该方法是否包含MyAnnotation注解
        if (method.isAnnotationPresent(MyAnnotation.class)) {
            //获取该方法的MyAnnotation注解实例
            MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
            //执行该方法
            method.invoke(annotationTest2, new Object[]{});
            //获取myAnnotation
            String[] value1 = myAnnotation.value1();
            System.out.println(value1[0]);
        }
        //获取方法上的所有注解
        Annotation[] annotations = method.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}

限定注解的使用:

限定注解使用

@Target

指示注释类型所适用的程序元素的种类

如果注释类型声明中不存在 @Target 元注释, 则声明的类型可以用在任一程序元素上

如果存在这样的元注释, 则编译器强制实施指定的使用限制

例如, 此元注释指示该声明类型是其自身, 即元注释类型, 它只能用在注释类型声明上:


@Target(ElementType.ANNOTATION_TYPE)
public @interface MetaAnnotationType {
    //...
}

指定该注释可注释多种类型:


@Target({ElementType.FIELD, ElementType.METHOD})
public @interface MemberType {
    //...
}

程序元素类型.此枚举类型的常量提供了 Java 程序中声明的元素的简单分类. 这些常量与 @Target 元注释类型一起使用,以指定在什么情况下使用注释类型是合法的

ANNOTATION_TYPE
注释类型声明
CONSTRUCTOR
构造方法声明
FIELD
字段声明(包括枚举常量)
LOCAL_VARIABLE
局部变量声明
METHOD
方法声明
PACKAGE
包声明
PARAMETER
参数声明
TYPE
类、接口(包括注释类型)或枚举声明

注解的使用限定的例子:


@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String[] value1() default "abc";
}

在帮助文档中加入注解:

要想在制作 JavaDoc 文件的同时将注解信息加入到 API 文件中, 可以使用 java.lang.annotation.Documented

在自定义注解中声明构建注解文档:


@Documented
public @interface MyAnnotation {

    String[] value1() default "abc";
}

在注解中使用继承:

默认情况下注解并不会被继承到子类中, 可以在自定义注解时加上java.lang.annotation.Inherited 注解声明使用继承

@Inherited

指示注释类型被自动继承

如果在注释类型声明中存在 @Inherited 元注释,并且用户在某一类声明中查询该注释类型, 同时该类声明中没有此类型的注释, 则将在该类的超类中自动查询该注释类型

此过程会重复进行,直到找到此类型的注释或到达了该类层次结构的顶层 (Object) 为止

如果没有超类具有该类型的注释, 则查询将指示当前类没有这样的注释

注意, 如果使用注释类型注释类以外的任何事物, 此元注释类型都是无效的

还要注意, 此元注释仅促成从超类继承注释; 对已实现接口的注释无效