16.1 基本Annotation
JDK1.5开始,Java增加了对元数据的支持,也就是Annotation。Annotation必须使用工具来处理,工具负责提取Annotation里包含的元数据。
元数据:修饰数据的数据,多个元数据包装在Annotation中修饰同一个数据。
程序元素:类、构造器、方法、成员变量等都是程序元素。
堆污染:把一个不带泛型的对象赋给一个带泛型的变量时,就会发生“堆污染”。在Java6及以前前,编译发生堆污染的方法,没有警告,但是调用发生堆污染的方法时,编译器就会发出警告;在Java7及以后,编译发生堆污染的方法一定会发出“堆污染”警告。
函数式接口:接口中只有一个抽象方法的接口就是函数式接口,但可以包含多个默认方法和多个类方法。
Java提供了5个基本的Annotation,位于java.lang包下:
- @Override(限定重写父类方法):用来指定方法重载,强制一个子类必须覆盖父类的方法。借助此注解,编译器会在编译时进行严格的检查,避免子类中出现重写方法的方法名错误等问题。@Override注解只能修饰方法。
- @Deprecated(标示已过时):用于表示某个程序元素已过时,当其他程序使用这些元素时,编译器会给出警告。
- @SuppressWarnings(抑制编译器警告):指示该Annotation修饰的元素取消显示指定的编译器警告,注解会从被注释的类一直作用到类里的方法和成员变量等。要在注解的括号内为value赋值,value的值为需要关闭的编译器警告。
- @SafeVarargs(抑制堆污染警告):此注解Java7开始存在,发生堆污染的代码使用此注解后,编译时程序不会发出任何警告。
- @FunctionInterface(函数式接口):此注解Java8开始存在,用来指定某个接口必须是函数式接口,通知编译器惊醒更严格的检查。@FunctionInterface只能修饰接口。
16.2 元Annotation
JDK在java.lang.annotation包下提供了6个元Annotation,其中有5个专门修饰Annotation定义。以下为4个常用的元注解。
16.2.1 @Retention
@Retention只能修饰Annotation定义,用于指定被修饰的Annotation可以保留多长时间。
@Retention的value成员变量值只能是如下三个之一:
- RetentionPolicy.CLASS:编译器把注解记录在class文件中,运行时消失。这是默认值。
- RetentionPolicy.RUNTIME:编译器把注解记录在class文件中,运行时JVM可以获取注解信息,程序可以通过反射获取注解信息。
- RetentionPolicy.SOURCE:注解只保留在源代码中,编译器会直接丢弃这个Annotation。
16.2.2 @Target
@Target只能修饰一个Annotation定义,用于指定被修饰的注解能用于修饰哪些程序单元。
@Target的value成员变量值只能是如下几种:
- ElementType.ANNOTATION_TYPE:指定该策略的Annotation只能修饰Annotation。
- ElementType.CONSTRUCTOR:指定该策略的Annotation只能修饰构造器。
- ElementType.FIELD:指定该策略的Annotation只能修饰成员变量。
- ElementType.LOCAL_VARIABLE:指定该策略的Annotation只能修饰局部变量。
- ElementType.METHOD:指定该策略的Annotation只能修饰方法定义。
- ElementType.PACKAGE:指定该策略的Annotation只能修饰包定义。
- ElementType.PARAMETER:指定该策略的Annotation可以修饰参数。
- Elementiype.TYPE:指定该策略的Annotation可以修饰类、接口(包括注解类型)或枚举定义。
16.2.3 @Documented
@Documented修饰的注解会被javadoc工具提取到API文档中。
16.2.4 @Inherited
@Inherited修饰的注解具有继承性。如果一个类使用了被@Inherited修饰的注解,则其子类将自动被那个注解修饰。
16.3 自定义Annotation
16.3.1 定义Annotation
定义新的Annotation类型使用@interface关键字,与定义一个接口的方式相似。定义Annotation是可以带有成员变量,成员变量以抽象方法来声明,方法名和返回值类型分别定义了变量名和类型可以通过default为成员变量指定默认值。设定了默认值的成员变量可以在使用时不指定值,没有设置默认值的成员变量必须在使用注解时传入值。
完整的定义过程:public @MyTag { String name(); default "wang" }
根据Annotation中是否包含成员变量,吧Annotation分为两类:
- 标记Annotation:没有定义成员变量的Annotation类型被称为标记。这种Annotation仅利用自身的存在与否来提供信息。如@Override和@Test等。
- 元数据Annotation:包含成员变量的Annotation,因为可以接受元数据,所以被称为元数据Annotation。
16.3.2 提取Annotation信息
使用@interface定义的注解,默认继承了Annotation接口。即Java使用Annotation接口来代表程序元素前面的注解,该接口是所有注解的父接口。
Java5在java.lang.reflect包里新增的AnnotatedElement接口,是所有程序元素(Class、Constructor、Field、Method、Package)的父接口,程序可以通过反射获取某个类的AnnotatedElement对象,程序就可以调用如下方法访问Annotation信息:
- <A extends Annotation> A getAnnotation(Class<A> annotationClass):返回该程序上存在的、指定类型的注解,如果此类型注解不存在则返回null。
- <A extendsAnnotation> A getDeclaredAnnotation(Class<A> annotationClass):Java8新增,该方法尝试获取直接修饰该程序元素、指定类型的Annotation。不存在返回null。
- Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
- Annotation[] getDeclaredAnnotations():返回直接修饰该元素的所有Annotation。
- boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):判断该程序元素上是否存在指定类型的注解,存在返回true,不存在返回false。
- <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass):由于Java8增加了重复注解,所以需要使用此方法获取修饰该元素、指定类型的多个Annotation。
- <A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass):由于Java8增加了重复注解,所以需要使用此方法获取直接修饰该元素、指定类型的多个Annotation。
如果需要获取某个注解里的元数据,则可以将注解强制类型转换为所需的注解类型,然后通过注解对象的抽象方法来访问这些元数据。如((MyTag) e).name();
为了让注解起作用,必须为这些注解提供一个注解处理工具方法。
16.4 Java8新增的重复注解
Java8以前,同一个程序元素只能使用一个相同类型的Annotation;如果需要在同一个元素前使用多个相同类型的Annotation,则必须使用Annotation容器。Java8以后允许使用多个相同类型的注解。开发重复注解需要使用@Repeatable修饰。注解容器保留期应该比重复注解的保留期要长。
定义举例:
- @Repeatable(MyTags.class) public @interface MyTag { int age(); }
- public @interface MyTags { MyTag[] value(); }
使用方式:
- 可以使用传统方式:@MyTags({@MyTag(age = 1), @MyTag(age = 2)})
- 可以不使用容器包裹:@MyTag(age = 1) @MyTag(age = 2)
不使用容器包裹只是一种假象,当程序获取修饰某个程序元素的注解时,依旧能获取MyTags.class。
16.5 Java8新增的Type Annotation
Java8为ElementType枚举新增了TYPE_PARAMETER、TYPE_USER两个枚举值,可以使用这两个枚举值为@Target赋值。使用@Target(ElementType.USE)修饰的注解被称为类型注解,类型注解可以在任何用到类型的地方出现。
Java8以前注解只能出现在程序元素前,Java8以后注解也可以出现在任何用到类型的地方:
- 创建对象,new @Nutnull Nemo();。
- 类型转换,(@NotNull Nemo) nemo;。
- 使用implement实现接口,implements @NotNull。
- 使用throws声明抛出异常,throw @NotNull Exception;。
目前Java8还未对类型注解提供检查框架。
16.6 编译时处理Annotation
APT(Annotation Processing Tool)是一种注解处理工具,他对源代码文件进行检测,并找出文件所包含的Annotation信息。可以生成额外的源文件和其他文件用于存放注解信息,编译时需要使用-processor选项。
每个Annotation处理器都需要实现javax.annotation.processing包下的Processor接口,或者继承AbstractProcessor抽象类。