您的位置:首页 > 编程语言 > Java开发

16. Java Annotation(注释)

2014-09-04 13:57 656 查看

本章目标:

了解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中都有各自的定义:

Java内定的Annotation

AnnotationJava中的声明
@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中的关键字如下表所示:

@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 习题


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: