注解
1、注解(Annotation)的概念和作用:
从 JDK5 开始,java 增加了对注解的支持。注解是代码中的特殊标记,可以在编译、类加载、运行时被读取,并执行相应的处理。
通过使用注解,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息,代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或部署。
注解就像修饰符一样,可以用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在注解的 “name=value” 对中。
注解并不会影响程序代码的执行,无论增加或删除注解,代码都始终如一的执行,如果希望程序中的注解在运行时起一定的作用,只有通过某种配套的工具对注解中的信息进行访问和处理,访问和处理注解的工具统称 APT(Annotation Precessing Tool)。
2、java中的常用注解:
基本注解(定义在 java.lang 包中)
@Override : 限定重写父类(包括实现的接口)方法,用于强制子类必须覆盖父类的方法。
告诉编译器检查被修饰的方法,保证父类要包含一个被该方法重写的方法,否则就会编译出错,帮助程序员避免方法名拼写错误而导致bug。
@Override 只能用于修饰方法,不能修饰其他程序元素。
@Deprecated : 标记已过时。
用于表示某个程序元素已过时,当其他程序员使用被标记为已过时的程序元素时,编译器将会给出警告。
@Deprecated 可用于修饰类、接口、方法、变量。
@SuppressWarnings : 抑制编译器警告。
表示被修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告。
通过 value 属性设置指定的编译器警告,如: @SuppressWarnings(value=“unchecked”)
@SafeVarargs(java7 中新增):专门为抑制 ”堆污染“ 警告提供的。
@SafeVarargs 可用于修饰方法、构造器。
@FunctionalInterface(java8 中新增):指定某个接口必须是函数式接口(如果接口中只有一个抽象方法(可以包含多个默认方法或多个静态方法),则该接口就是函数式接口)。
用于告诉编译器检查修饰的接口,保证该接口只能包含一个抽象方法,否则就会编译出错。
@FunctionalInterface 只能修饰接口,不能修饰其他程序元素。
元注解(定义在 java.lang.annotation 包中)
@Retention :指定被修饰的注解可以保留多长时间。
@Retention 包含一个 RetentionPolicy 类型的 value 成员变量,使用该注解时,必须为该 value 成员变量指定值,value 成员变量的值:
(1)RetentionPolicy.CLASS : 编译器把被修饰的注解记录在 class 文件中,当运行程序时,JVM 不可获取注解信息,这是默认值。
(2)RetentionPolicy.RUNTIME : 编译器把被修饰的注解记录在 class 文件中,当程序运行时,JVM 可获取注解信息,程序可以通过反射获取该注解信息。
(3)RetentionPolicy.SOURCE : 被修饰的注解只保留在源代码中,编译器直接丢弃这种注解。
综上可知,如果需要通过反射获取注解信息,就必须使用 value 属性值为 RetentionPolicy.RUNTIME 的 @Retention。
@Retention 只能用于修饰注解定义。
@Target : 指定被修饰的注解可用于修饰哪些程序元素。
@Target 包含一个 ElementType 类型的 value 成员变量,使用该注解时,必须为该 value 成员变量指定值,value 成员变量的值:
(1)ElementType.ANNOTATION_TYPE : 指定被修饰的注解只能修饰注解定义。
(2)ElementType.CONSTRUCTOR : 指定被修饰的注解只能修饰构造器。
(3)ElementType.FIELD : 指定被修饰的注解只能修饰成员变量。
(4)ElementType.LOCAL_VARIABLE : 指定被修饰的注解只能修饰局部变量。
(5)ElementType.METHOD : 指定被修饰的注解只能修饰方法。
(6)ElementType.PACKAGE : 指定被修饰的注解只能修饰包。
(7)ElementType.PARAMETER : 指定被修饰的注解只能修饰参数。
(8)ElementType.TYPE : 指定被修饰的注解可以修饰类、接口(包括注解类型)、枚举定义等类型声明。
(9)ElementType.TYPE_PARAMETER :指定被修饰的注解只能修饰类型参数。
(10)ElementType.TYPE_USE :指定为这种类型的注解,被称为类型注解,类型注解可以用在任何用到类型的地方。
@Target 只能用于修饰注解定义。
@Documented :指定被修饰的定义注解类将被 javadoc 工具提取成文档。
如果定义注解类时使用了 @Documented 修饰,则所有被定义注解所修饰的程序元素的 API 文档中都将会包含定义注解的说明。
@Documented 只能用于修饰注解定义。
@Inherited :表示被修饰的定义注解具有继承性。
如果某个类使用了定义注解修饰,则被修饰类的子类也将自动被定义注解修饰。
@Repeatable :表示被修饰的定义注解是一个重复注解(java8新增)。
java8 以前,同一个程序元素前最多只能使用一个相同类型的注解,如果需要在同一个程序元素前使用多个同类型的注解,则必须使用注解容器。例如:
@MyTags({@MyTag(name = "lisi", age = 22),
@MyTag(name = "zhangsan", age = 33)})
public class TestDemo {
}
java8 新增了一个 @Repeatable 元注解,用于定义重复注解。
使用 @Repeatable 修饰定义注解时,必须为它的 value 成员变量指定值,该成员变量的值是一个容器注解,该容器注解可以包含多个定义注解,因此还需要定义一个容器注解。
注:容器注解的保留期必须大于或等于它所包含的注解的保留期,否则编译器会报错。这是因为我们需要通过容器注解来获取被包含的注解。
定义重复注解的示例:
// MyTag.class 文件:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(MyTags.class)
public @interface MyTag {
String name();
int age();
}
// MyTags.class 文件:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTags {
MyTag[] value();
}
// TestDemo.class 文件:
@MyTag(name = "lisi", age = 22) @MyTag(name = "zhangsan", age = 33) public class TestDemo { public static void main(String[] args) { MyTag[] tags = TestDemo.class.getDeclaredAnnotationsByType(MyTag.class); for (MyTag tag : tags) { System.out.println(tag.name() + " : " + tag.age()); } /* * 上面输出: * lisi : 22 * zhangsan : 33 * */ MyTags container = TestDemo.class.getDeclaredAnnotation(MyTags.class); System.out.println(container); /* * 上面输出: * @com.test.repetition.MyTags(value=[@com.test.repetition.MyTag(name=lisi, age=22), @com.test.repetition.MyTag(name=zhangsan, age=33)]) * */ MyTag tag = TestDemo.class.getDeclaredAnnotation(MyTag.class); System.out.println(tag); /* * 上面输出: * null * */ } }
3、自定义注解:
定义新的注解类型使用关键字 @interface ,如下代码可定义一个简单的注解类型:
public @interface Test {
}
注解中的成员变量,以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型,如下,定义一个成员变量:
public @interface Test {
//定义两个成员变量
// 名字为 name ,类型为 String
String name();
// 名字为 age , 类型为 int
int age();
}
可以在定义注解的成员变量时为其指定初始值(默认值),指定成员变量的初始默认值,使用关键字 default ,如:
public @interface Test {
//定义两个成员变量
// 名字为 name ,类型为 String
String name() default "lisi";
// 名字为 age , 类型为 int
int age() default 21;
}
一旦在注解中定义了成员变量后,使用该注解时,就应该为注解的成员变量指定值(注解定义时没有为成员变量指定默认值),例如:
public class TestAnnotation {
// 使用带成员变量的注解时,需要为成员变量赋值
@Test(name = "zhangsan", age = 22)
public void info() {
}
}
如果定义注解时,为注解的成员变量指定了默认值,则使用该注解时,可以不为有默认值的成员变量赋值,如:
public @interface Test { //定义两个成员变量 // 名字为 name ,类型为 String String name() default "lisi"; // 名字为 age , 类型为 int int age() default 21; }
// 可以不为成员变量赋值,此时成员变量使用默认值。 也可以为成员变量赋值,赋值后,成员变量的默认值将不会起作用
@Test
public void info() {
}
根据注解是否可以包含成员变量,可以把注解分为如下两类:
标记注解:没有包含成员变量的注解,被称为标记注解。这种注解利用自身的存在与否来提供信息,如 @Override 。
元数据注解:包含成员变量的注解,可以接收更多的元数据。
4、提取注解信息:
使用注解修饰了类、方法、成员变量等程序元素后,这些注解并不会自己生效,必须由开发者提供相应的工具来提取并处理注解信息。
java 中,使用 Annotation 接口来代表程序元素前面的注解,该接口是所有注解的父接口。
java中,使用 java.lang.reflect 包下的 AnnotatedElement 接口来代表程序中可以接收注解的程序元素,该接口主要有如下实现类:
Class :类定义
Constructor :类的构造器定义
Field: 类的成员变量定义
Method :类的方法定义
Package:类的包定义
只有在定义注解时使用了 @Retention(RetentionPolicy.RUNTIME) 修饰,该注解在运行时才可见,JVM 才会在装载 class 文件时,读取保存在 class 文件中的注解。
AnnotatedElement 接口定义了如下方法,用来访问注解的信息:
// 返回该程序元素上存在的、指定类型的注解,如果该类型的注解不存在,则返回null
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
// java8 新增方法,该方法尝试获取直接修饰该程序元素、指定类型的注解,如果该类型的注解不存在,则返回null
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
// 返回该程序元素上存在的所有注解
Annotation[] getAnnotations()
// 返回直接修饰该程序元素的所有注解
Annotation[] getDeclaredAnnotations()
// 判断该程序元素上是否存在指定类型的注解
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
// 获取修饰该程序元素、指定类型的多个注解
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)
// 获取直接修饰该程序元素、指定类型的多个注解
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)