本章目标:
- 了解Annotation的作用
- 掌握JDK1.5种内建的3种Annotation的使用
- 掌握自定义Annotation的语法及其应用
- 掌握@Retention、@Target、@Documented、@Inherited注释
Annotation的概念和作用、@Override注释的功能和用法、@Deprecated注释的功能和用法、@SuppressWarnings注释的功能和作用、自定义注释、提取注释信息、@Retention注释的功能和用法、@Target注释的功能和用法、@Documented注释的功能和用法、@Inherited注释的功能和用法、使用APT工具
16.1 Annotation简介
从JDK1.5开始,JAVA增加了对元数据(Metadata)的支持,也就是Annotation(注释),这种Annotation与其他的注释有一定的区别,也有一定的联系。本章所讲的Annotation,其实就是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。
通过使用Annotation注释,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或进行部署。
Annotation提供了一种为程序元素设置元数据的方法,从某些方面来看,Annotation就像修饰符一样,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在Annotation的“name=value”对中。
Annotation不影响程序运行,无论是否使用Annotation代码都可以正常运行。如果希望让程序中的Annotation在运行时起一定的作用,只能通过某种配套的工具对Annotation中的信息进行访问和处理,访问和处理Annotation的工具统称为APT(Annotation Processing Tool)。
*****Annotation是一个接口(java.lang.annotation.Annotation),程序可以通过反射来获取指定程序元素的Annotation对象,然后通过Annotation对象来取得注释里的元数据*****
java.lang.annotation.Annotation接口的定义:
public interface Annotation{
public Class<? extends Annotation> annotationType(); //返回此annotation的注释类型
public boolean equals(Object obj);
public int hashCode();
String toString();
}
16.2 系统内建的Annotation
Annotation必须使用工具来处理,工具负责提取Annotation里包含的元数据,工具还会根据这些元数据增加额外的功能。我们先看一下Java提供的4个基本Annotation的用法 --- 使用Annotation时要在其前面增加@符号,并把该Annotation当成一个修饰符使用,用于修饰它支持的程序元素。
- @Override: 覆写的Annotation
- @Deprecated: 不赞成使用的Annotation
- @SuppressWarnings: 压制安全警告的Annotation
- @SafeVarargs: 压制安全警告
以上的Annotation都是java.lang.annotation.Annotation接口的子类。在Java中都有各自的定义:
Annotation | Java中的声明 |
@Override | @Target(value=Method) @Retention(value=SOURCE) public @interface Override |
@Deprecated | @Documented @Retention(value=RUNTIME) public @interface Deprecated |
@SuppressWarnings | @Target(value={TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE @Retention(value=SOURCE) public @interface SuppressWarnings |
16.2.1 @Override 限定重写父类方法
@Override就是用来指定方法覆载的,它可以强制一个子类必须覆盖父类的方法。主要在方法覆写时使用,用于保证方法覆写的正确性。
范例:观察@Override注释的作用
package org.forfan06.annotationdemo;
class Person{
public String getInfo(){
return "这是一个Person类";
}
}
class Student extends Person{
@Override
public String getInfo(){
return "这是一个Student类";
}
}
public class OverrideAnnotationDemo01{
public static void main(String args[]){
Person per = new Student();
System.out.print(per.getInfo());
}
}
运行结果:
这是一个Student类
以上程序中的子类Student继承Person类,之后覆写了Person类中的getInfo()方法,程序运行结果和之前的一样,唯一的不同只是在覆写的getInfo()方法前加上了@Override注释。这样做的目的是防止用于在覆写方法是将方法定义出错。
下面演示一个方法覆写错误的程序,观察编译信息
范例:错误的覆写
package org.forfan06.annotationdemo;
class Person{
public String getInfo(){
return "这是一个Person类";
}
}
class Student extends Person{
@Override
public String getinfo(){
return "这是一个Student类";
}
}
public class OverrideAnnotationDemo02{
public static void main(String args[]){
Person per = new Student();
System.out.print(per.getInfo());
}
}
编译时出错:会在出错的地方给出指示,因为getInfo()和getinfo()是两个方法,所以使用了@Override注释,可以确保方法被正确覆写
/judge/data/20140904/1409815346955_java_34546/OverrideAnnotationDemo02.java:8: error: method does not override or implement a method from a supertype
@Override
^
1 error
编译错误
================================================================================
@Override的使用限制:@Override在使用时只能在方法上应用;而其他元素,如类、属性等上市不能使用此Annotation的。
================================================================================
16.2.2 @Deprecated 标示已过时
@Deprecated注释用于表示某个程序元素(类、方法等)已过时,当其他程序使用已过时的类、方法时,编译器将会给出警告。
@Deprecated注释的主要功能是用来声明一个不建议使用的方法。如果在程序中使用了此方法,则在编译时将会出现警告信息。
范例:使用@Deprecated声明一个不建议使用的方法
package org.forfan06.annotationdemo;
class Demo{
@Deprecated
public String getInfo(){
return "被测试类";
}
}
public class DeprecatedAnnotationDemo01{
public static void main(String args[]){
Demo d = new Demo();
System.out.println(d.getInfo());
}
}
以上的Demo类中的getInfo()方法上使用了@Deprecated注释声明,表示此方法不建议用户继续使用,所以在编译时将会出现一下的警告信息:
Note: /judge/data/20140904/1409815853025_java_34546/DeprecatedAnnotationDemo01.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
虽然出现了警告信息,但是程序还是可以正常执行。因为@Deprecated注释只是表示该方法不建议使用,但并不是不能使用!!!
-->@Deprecated注释除了可以声明方法之外,还可以声明一个类<--
范例:在类声明中使用@Deprecated注释
package org.forfan06.annotationdemo;
@Deprecated
class Demo{
public String getInfo(){
return "测试测试";
}
}
public class DeprecatedAnnotationDemo02{
public static void main(String args[]){
Demo d = new Demo();
System.out.println(d.getInfo());
}
}
编译时出现警告:
Note: /judge/data/20140904/1409816274992_java_34546/DeprecatedAnnotationDemo02.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
*****Thread类中的@Deprecated声明,在Thread类中有3个方法是使用了@Deprecated注释声明的: suspend()、stop()、resume()*****
*****@Deprecated的作用与文档注释中的@deprecated标记的作用基本相同,但它们的用法不同,前者是JDK1.5才支持的注解,无须放在文档注释语法(/**...**/)中,而是直接用于修饰程序中的程序单元,如方法、类、接口等等*****
16.2.3 @SuppressWarnings 抑制编译器警告
@SuppressWarnings指示被该Annotation修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告。@SuppressWarnings会抑制作用于该程序元素的所有子元素。例如,使用@SuppressWarnings修饰某个类取消显示某个编译器警告,同时又修饰该类中的某个方法取消显示另一个编译器警告,那么该方法将会同时取消显示这两个编译器警告。
@SuppressWarnings注释的主要功能是用来压制警告,例如,之前讲解泛型操作时,如果在一个类声明时没有指明泛型,则肯定在编译时产生,那么此时就可以使用@SuppressWarnings压制住这种警告。
在通常情况下,如果程序中使用没有泛型限制 的集合将会引起编译器警告,为了避免这种编译器警告,可以使用@SuppressWarnings修饰。
package org.forfan06.annotationdemo;
class Demo<T>{
private T var;
public T getVar(){
return var;
}
public void setVar(T var){
this.var = var;
}
}
public class SuppressWarningsAnnotationDemo01{
@SuppressWarnings(value="unchecked")
public static void main(String args[]){
Demo d = new Demo();
d.setVar("forfan06");
System.out.println("Content is:" + d.getVar());
}
}
程序在声明Demo对象时,并没有指定具体的泛型类型。如果没有@SuppressWarnings注释修饰的话,会出现一下警告信息:
Note: /judge/data/20140904/1409817887476_java_34546/SuppressWarningsAnnotationDemo01.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
但是由于使用了@SuppressWarnings注释,所以此警告信息将不会出现。
在@SuppressWarnings注释中的unchecked,表示的是不检查。当然,如果现在需要压制更多地警告信息,可以在后面继续增加字符串,只是在增加时,要按照数组的格式增加。
范例:压制多个警告
package org.forfan06.annotationdemo;
@Deprecated
class Demo<T>{
private T var;
public T getVar(){
return var;
}
public void setVar(T var){
this.var = var;
}
}
public class SuppressWarningsAnnotationDemo02{
@SuppressWarnings({"unchecked", "deprecation"})
public static void main(String args[]){
Demo d = new Demo();
d.setVar("forfan06");
System.out.println("Content is:" + d.getVar());
}
}
上面程序同时存在了泛型和不建议使用方法两种警告信息,但是由于使用了@SuppressWarnings注释,所以此时程序在编译时将不会出现任何的警告信息。
@SuppressWarnings中的关键字如下表所示:
关键字 | 描述 |
deprecation | 使用了不赞成使用的类或方法时的警告 |
unchecked | 执行了未检查的转换时警告。例如,泛型操作中没有指定泛型类型 |
fallthrough | 当使用switch操作时case后未加入break操作,而导致程序继续执行其它case语句时出现的警告 |
path | 当设置了一个错误的类路径、源文件路径时出现的警告 |
serial | 当在可序列化的类上缺少serialVersionUID定义时的警告 |
finally | 任何finally子句不能正常完成时的警告 |
all | 关于以上所有情况的警告 |
-->另外,在设置注释信息时,是以key-value的形式出现的。所以以上的@SuppressWarnings也可以直接使用“value = {"unchecked", "deprecation"}” 的方式设置<--
范例:另外一种形式的@SuppressWarnings
package org.forfan06.annotationdemo;
@Deprecated
class Demo<T>{
private T var;
public T getVar(){
return var;
}
public void setVar(T var){
this.var = var;
}
}
public class SuppressWarningsAnnotationDemo03{
@SuppressWarnings(value = {"unchecked", "deprecation"})
public static void main(String args[]){
Demo d = new Demo();
d.setVar("forfan06");
System.out.println("Content is:" + d.getVar());
}
}
16.2.4 @SafeVarargs与Java 7的“堆污染”
在泛型擦除时,下面代码可能导致运行时异常
List list = new ArrayList<Integer>();
list.add(20); //添加元素时引发unchecked异常
//下面代码引起“未经检查的转换”的警告,编译、运行时完全正常
List<String> ls = list; //***
//但是只要访问ls里的元素,则会引用运行时异常
System.out.println(ls.get(0));
Java把引发这种错误的原因成为 “ 堆污染 ”(Heap Pollution) ,当把一个不带泛型的对象赋值给一个带泛型的变量时,往往就会发生这种 “ 堆污染 ”。
对于形参个数可变的方法,该形参的类型又是泛型,这将更容易导致 “ 堆污染 ”。
public class ErrorUtils{
public static void faultyMethod(List<String>... listStrArray){
//Java语言不允许创建泛型数组,因此listArray只能被当成List[]处理
//此时相当于把List<String>赋给了List,已经发生了 “ 堆污染 ”
List[] listArray = listStrArray; //Line5
List<Integer> myList = new ArrayList<Integer>();
myList.add(new Random().nextInt(100));
//把listArray的第一个元素赋为myArray
listArray[0] = myList;
String s = listStrArray[0].get(0);
}
}
上面程序代码中Line5处发生了 “ 堆污染 ” 。由于该方法有个形参是List<String>...类型,个数是可变的形参相当于数组。但是,Java又不支持泛型数组,因此程序只能把List<String>...当成List[]处理。这就发生了 堆污染。
如果,不希望看到这个警告,可以使用如下3种方式来 “压制”这个警告:
- 使用@SafeVarargs修饰引发该警告的方法或构造器
- 使用@SuppressWarnings("unchecked")修饰
- 编译时使用-Xlint:varargs选项
第3种方式一般比较少用,通常选择第1种或第2种方式,尤其是使用@SafeVarargs修饰引发该警告的方法或构造器,它是Java 7专门为压制“堆污染”警告提供的。
16.3 JDK的元Annotation
JDK除了在java.lang下提供4个基本的Annotation之外,还在java.lang.annotation包下提供了4个Meta Annotation(元Annotation),这4个元Annotation都用于修饰其他的Annotation定义。
16.3.1 @Retention
@Retention只能用于修饰一个Annotation定义,用于指定被修饰的Annotation可以保留多长时间(保存范围)。 @Retention包含一个RetentionPolicy类型的value成员变量,所以使用@Retention时必须为该value成员变量指定值
//@Retention的定义:
@Documented
@Rentention(value=RUNTIME)
@Target(value=ANNOTATION_TYPE)
public @interface Retention{
RetentionPolicy value();
}
Retention定义中的RetentionPolicy变量用于指定Annotation的保存范围。 其包含一下3个范围:
- RetentionPolicy.CLASS: 编译器将把Annotation记录在class文件中。当运行Java程序时,JVM不再保留Annotation。********此Annotation类型将保留在程序源文件(*.java)和编译之后的类文件(*.class)中。在使用此类时,这些Annotation信息不会被加载到虚拟机(JVM)中。如果一个Annotation声明时没有指定范围,则默认是此范围********
- RetentionPolicy.RUNTIME: 编译器将把Annotation记录在class文件中。当运行Java程序时,JVM也会保留Annotation,程序可以通过反射获取该Annotation信息。********此Annotation类型的信息保留在源文件(*.java)、类文件(*.class)中,在执行时也会加载到JVM中********
- RetentionPolicy.SOURCE: Annotation只保留在源代码中,编译器直接丢弃这种Annotation。 *******此Annotation类型的信息只会保留在程序源文件(*.java)中,编译之后不会保存在编译好的类文件(*.class)中*********
==========如果Annotation里只有一个value成员变量,使用该Annotation时可以直接在Annotation后的括号里面指定value成员变量的值,无须使用name=value形式=======
Java内建的Annotation的范围:
- @Override定义采用的是@Retention(value=SOURCE),只能在源文件中出现
- @Deprecated定义采用的是@Retention(value=RUNTIME),可以在执行时出现
- @SuppressWarnings定义采用的是@Retention(value=SOURCE),只能在源文件中出现
范例:定义在RUNTIME范围有效的Annotation
package org.forfan06.annotationdemo;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(value=RetentionPolicy.RUNTIME) //此Annotation在执行时起作用
public @interface MyDefaultRetentionAnnotation{
public String name() default "forfan06" ;// 只能设置枚举的取值
}
上面定义的Annotation在程序执行时起作用,这是一种比较常见的使用方式,而如果此时将其设置成其他范围,则以后再Annotation的应用中肯定是无法访问到的。
要想让一个Annotation起作用,必须结合Java中的反射机制。
16.3.2 @Target
16.3.3 @Documented
16.3.4 @Inherited
16.4 自定义Annotation
16.4.1 定义Annotation
16.4.2 提取Annotation信息
16.4.3 使用Annotation的示例
16.5 编译时处理Annotation
16.6 通过反射取得Annotation
16.6.1 取得全部的Annotation
16.6.2 取得指定的Annotation中的内容
16.7 本章要点
16.9 习题