14.1 基本Annotation
- Annotation与Class、Interface、Enum同级
- Annotation为代码中特殊标记,类编译、加载时读取,并执行相应处理
- Annotation不影响程序代码执行,如果希望Annotation起一定作用,需要使用APT(Annotation Procession Tool)对Annotation中信息进行访问与处理
- 使用Annotation
- 使用上与访问修饰符public、private等相同
- Annotation名前加"@",用于修饰它支持的元素
- 应该为该Annotation的成员变量指定值
- Annotation独占一行
14.1.1 限定重写父类方法:@Override
- 只能修饰方法
- 修饰的方法如果未构成重写,编译报错
14.1.2 标示已过时:@Deprecated
- 修饰的方法,被使用时,编译器提示警告。对于eclipse,调用过时方法时,该方法会被划横线提示,例:
test()
14.1.3 抑制编译器警告:@SuppressWarnings
- 修饰的元素以及该元素下所有子元素,编译器警告被取消
- 必须指定其成员变量value的值
@SuppressWarnings(value="unchecked")
14.1.4 抑制编译器堆污染警告:@SafeVarargs
- 取消编译器的堆污染警告
- 泛型的"转换"就是一种堆污染
14.1.5 Java8的函数式接口与@FunctionalInterface
- 其修饰的接口如果不是函数式接口,编译报错
- 函数式接口:只有一个抽象方法的接口(可以包含多个默认方法或多个static方法)
14.2 JDK的元Annotation
用于修饰其他Annotation的Annotation,位于java.lang.annotation包下
14.2.1 @Retention
- 指定被修饰的Annotation所保留的时间
package test.wsh;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
//1. 名为Retention的Annotation修饰名为Testable的Annotation
//RetentionPolicy.RUNTIME:把Testable记录在class中。java运行时,JVM也可获取Annotation信息,程序可以通过反射获取该Annotation信息。需要在运行时去动态获取注解信息时使用
//RetentionPolicy.CLASS:把Testable记录在class中。java运行时,JVM不可获取Annotation信息,为默认值。在编译时进行一些预处理操作,比如生成一些辅助代码时使用
//RetentionPolicy.SOURCE:把Testable只记录在源码中。编译器直接丢掉这种Annotation。例: @Override 和 @SuppressWarnings
@Retention(value = RetentionPolicy.RUNTIME)
//2. 如果只为Annotation中属性value进行赋值,可以省略"value="
//@Retention(RetentionPolicy.RUNTIME)
//3. 定义一个Annotation名为Testable
public @interface Testable {
}
14.2.2 @Target
- 指定其修饰的Annotation能用于修饰哪些程序元素
package test.wsh;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
//ElementType.ANNOTATION_TYPE:ActionListenerFor只能修饰Annotation
//ElementType.CONSTRUCTOR:只能修饰构造器
//ElementType.FIELD:只能修饰成员变量
//ElementType.LOCAL_VARIABLE:只能修饰局部变量
//ElementType.METHOD:只能修饰方法
//ElementType.PACKAGE:只能修饰包
//ElementType.PARAMETER只能修饰参数
//ElementType.TYPE:只能修饰类、接口、Annotation、枚举类
@Target(value = ElementType.FIELD)
public @interface ActionListenerFor {
}
14.2.3 @Documented
- @Documented修饰的Annotation所修饰的程序元素的API文档将会包含该Annotation说明
14.2.4 @Inherited
- @Inherited所修饰的Annotation,A,所修饰的类B,其子类C将自动被A修饰
14.3 自定义Annotation
14.3.1 定义Annotation
- 根据Annotation中是否包含成员变量进行分类
- 标记Annotation:不包含成员变量,仅利用自身是否存在来提供信息,例:@Override、@Test
- 元数据Annotation:包含成员变量,他们可在成员变量中存放元数据
package test.wsh;
public @interface MyTag {
//1. 定义成员变量name与age,并指定初值
String name() default "wusihan";
int age() default 32;
}
package test.wsh;
//若未指定初值,则使用时必须为其所有成员变量指定值
@MyTag(name="吴",age=6)
public class Test {
}
14.3.2 提取Annotation信息
- Annotation修饰程序元素后,不会自动生效,必须由开发者提供相应的工具来提取并处理Annotation信息
- java.lang.annotation.Annotation接口来表示程序元素前的注释,为所有注释的父接口
- java.lang.reflect.AnnotatedElement代表可以接受注释的程序元素,该接口主要有以下实现类Class、Field、Constructor、Method、Package,其中Annotation属于Class,其内成员变量属于Method,为获取AnnotatedElement对象必须使用反射知识,一旦获取到该对象,就可以调用其下方法访问Annotation信息,前提是修饰该程序元素的注释必须被@Retention(RetentionPolicy.RUNTIME)修饰。
//返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,返回null
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
//java8新增方法,返回直接修饰该程序元素的指定类型的Annotation
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
//返回该程序元素上存在的所有注释
Annotation[] getAnnotations()
//返回直接修饰的、所有的
Annotation[] getDeclaredAnnotations()
//判断该程序元素上是否存在指定类型的Annotation,存在返回true,否则为false
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
//java8增加了重复注释,因此需要该方法返回修饰该程序元素、指定类型的多个Annotation
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)
- 示例
package test.wsh;
import java.lang.annotation.Annotation;
//若未指定初值,则使用时必须为其所有成员变量指定值
@MyTag(name="吴",age=6)
public class Test {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, ClassNotFoundException {
//1. 反射,调用getMethod方法获取AnnotatedElement子类Method的对象
//2. 如果修饰info方法的@MyTag注释,没有被@Retention(RetentionPolicy.RUNTIME)修饰,getAnnotations方法返回null
//打印:@test.wsh.MyTag(name=吴, age=6)
Annotation[] aArray = Class.forName("test.wsh.Test").getMethod("info").getAnnotations();
for(Annotation an:aArray) {
System.out.println(an);
if(an instanceof MyTag) {
System.out.println(((MyTag)an).name());
}
}
}
@MyTag(name="吴",age=6)
public void info() {
}
}
14.3.3 使用Annotation的示例
14.3.4 Java8新增的重复注解
- Java8以前,同一个程序元素前,同一个Annotation A只能使用一次。若想多次使用同一个Annotation,需新定义一个Annotation B,其内属性为A[]
- 例:在Structs2开发时,在Action类上使用多个@Result注解
package test.wsh;
public @interface Result {
String name();
String location();
}
package test.wsh;
public @interface Results {
Result[] value();
}
package test.wsh;
//写法过于复杂
@Results({ @Result(name = "success", location = "succ.jsp"), @Result(name = "failure", location = "failed.jsp") })
public class FooAction {
}
- 使用@Repeatable允许@FkTag可以重复注释
package test.wsh;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
//1. 标注当前注释可以重复注释,对应容器为FkTags
@Repeatable(FkTags.class)
//2. 希望在內存中保存注释信息
@Retention(RetentionPolicy.RUNTIME)
public @interface FkTag {
}
package test.wsh;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
//1. FkTags容器的Retention范围要大于其容器内的注释FkTag的Retention,否则编译就会报错,因为容器的信息无法带入到JVM,其内元素的信息更无法带入到JVM
@Retention(RetentionPolicy.RUNTIME)
public @interface FkTags {
//2. 为FkTag容器,即属性为FkTag数组
FkTag[] value();
}
package test.wsh;
//1. 简化了写法,依然可以使用老写法
@FkTag(age=5, name = "含")
@FkTag(age=6, name = "低")
@FkTag(age=6, name = "调")
public class FkTagTest {
public static void main(String[] args) {
Class<FkTagTest> clazz = FkTagTest.class;
//2. java8新增方法,获取指定程序元素上的指定类型的Annotation数组
FkTag[] fk = clazz.getDeclaredAnnotationsByType(FkTag.class);
for(FkTag fklocal:fk) {
System.out.print(fklocal.name());
}
}
}
14.3.5 Java8新增的Type Annotation
- Java8以前只能在"定义程序元素"前时使用注解
- Java8以后,Type Annotation允许在任何用到类型的地方使用,与对程序元素使用注释不同,对类型的注释,必须写明@Target(ElementType.TYPE_USE),否则编译报错
package test.wsh;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.TYPE_USE)
public @interface NotNull {
}
package test.wsh;
import java.io.FileNotFoundException;
import java.io.Serializable;
import java.util.List;
import javax.swing.JFrame;
//1. 定义类、类实现接口
@NotNull
public class TypeAnnotationTest implements @NotNull Serializable{
//2. 声明抛出异常
public static void main(String[] args) throws @NotNull FileNotFoundException{
Object obj = "fkjava.org";
//3. 类型转换
String str = (@NotNull String)obj;
//4. 创建对象
Object win = new @NotNull JFrame("疯狂软件");
}
//5. 泛型中使用
public void Foo(List<@NotNull String> info) {
}
}
14.4 编译时处理Annotation
- APT(注释处理工具)其实就是一个继承Processor的Java类,由于Processor中抽象方法过多,一般也可以继承AbstractProcessor,只实现其下process抽象方法即可
- APT一般可用于在编译程序源代码同时生成一些附属文件
- 本例中自定义APT,名为HibernateAnnotationProcessor,模拟Hibernate对注释的处理,即可以在保存实体类时,根据其注释,在指定位置生成指定xml文件
//Person为实体类
javac -processor HibernateAnnotationProcessor Person.java
- 相关方法
//AbstractProcessor的方法。APT继承AbstractProcessor时,必须重写该方法,annotations不知道有什么用,roundEnv代表运行时环境,通过它可以获得该类上的Element(也代表程序元素与AnnotatedElement类似),从而可以进一步获取程序元素上Annotation信息
//我理解AnnotatedElement与Element区别在于,一个是运行时用的,一个是编译时用的,都代表程序元素,也都可以获取程序元素上的Annotation
boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
//RoundEnvironment的方法。获取所有被指定注释修饰的所有元素
Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a)
//Element的方法。获取所有被指定注释修饰的所有元素
<A extends Annotation> A getAnnotation(Class<A> annotationType)
//Element的方法。获取所有该程序元素下的所有程序元素,例:类元素下包含方法、变量等元素
List<? extends Element> getEnclosedElements()
//Element的方法。获取当前元素的类型,和ElementType类似
ElementKind getKind()
//Element的方法。返回元素名称
Name getSimpleName()