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

Java—给编译器看的注解—Annotation

2020-08-07 17:55 1186 查看

Annotation的含义

Annotation(中文翻译为“注解”,或“注释”)实际上表示的是一种注释的语法,这种注释和前面章节讲到的代码的注释是不一样的,代码的注释(如单行注释用双斜杠“//”,多行注释用“/…/等”)是给程序员看的,其主要目的是为了增加代码的可读性,便于代码的后期维护。而这里的Annotation,主要是服务于编译器,属于一种配置信息。早期的Java程序提倡程序与配置文件相分离,代码是代码,注释是注释,二者“井水不犯河水”,但后来的实践发现,配置文件过多,以至于配置信息修改起来非常困难,所以将配置信息直接写入到程序之中的理念又重新得到应用。

而若想要在Java中完成这样的功能,就需要使用Annotation。在流行的SSH框架(即:Spring——开源的Java/Java EE全功能栈的轻量级的应用程序框架),Struts2——用于开发Java EE网络应用程序的开源Web应用框架),Hibernate——Java语言下的对象关系映射解决方案,它对JDBC进行了轻量级的对象封装,是一种数据库持久层框架)等中大量使用了这个技术,所以大家有必要对此技术有所理解。

在本质上,Annotation 提供了一种与程序元素关联任何信息或者任何元数据(metadata)的方式。Annotation可以像修饰符一样被使用,它可以应用于任何程序元素(如包、类 型、构造方法、方法、成员变量、参数、本地变量)的声明中。这些信息被存储在Annotation的“name=value”结构对中。Annotation类型事实上是一种接口,能够通过Java反射API(应用程序接口)的方式提供对其信息的访问。

系统内建的Annotation

在JDK 1.5之后的系统中,内建了3个Annotation:@Override、@Deprecated、@SuppressWarnings,下面分别给予简要介绍。

@Override

如果要进行方法的覆写,那么要求是:方法名称、参数的类型及个数完全相同,而在开发之中有可能会由于手误等原因导致方法不能被正确地覆写,如下面的程序。

由于手误,导致覆写错误(OverrideErroe.java)


第3~6行原本打算覆写toString方法,却由于手误导致覆写“错误”——tostring(),其中的S字符被错误小写,而Java是区分大小写的,这时不会产生编译错误,因为JDK会认为tostring()是一个新的方法,可是从实际上来讲,这个方法应该是被覆写的,所以为了保证这种错误的问题在程序编译的时候就可以被发现,可以在方法覆写时增加上“@Override”定义。@Override用在方法之上,就是用来告诉编译器这个方法是改写父类的。

使用@Override Annotation(OverrideAnnotation.java)

第03行和第04~07行同样由于“手误”,并没有达到覆写toString方法的目的,但是在编译时就会发生编译错误(使用Eclipse会有错误提示),提示 “类型为Message 的方法 tostring()必须覆盖或实现超类型方法”,如下图所示,这样添加了注解,就可以及时在编译时就发现错误,并提示用户早点改正错误,以防日后维护困难。

如果将注解提示的错误纠正过来——也就是将范例中的第04行“public Stringtostring()”纠正为“public String toString()”,完成了真正的覆写,允许结果如下图所示。

@Deprecated

在API中经常会看到某个方法下面有“Deprecated”这个单词,表示这个方法已经过时,不建议使用,如String类的构造方法中就有这样一个方法。

这个过时的方法并不能将多个字节准确地转换为字符,自从 JDK 1.1 起,完成该转换的首选方法是通过 String 构造方法,该方法接受一个字符集名称或使用平台的默认字符集。

标识某个方法过时的功能可以使用@Deprecated 的注解来实现。参见下面的例子。

演示@Deprecated Annotation的效果(DeprecatedAnnotation.java)。


第12行使用@Deprecated注解,就是用来建议别人不要使用某些旧的方法(或API),这时编译的时候会产生警告信息,它也可以设定在程序中所有的元素上,来表明某个元素是过时的。

第05行使用info对象调用getInfo方法,并不会产生编译错误,程序可以正常运行,只是并不建议使用该方法,因为它已经过时。过时的方法还保留至今,这主要是为了保证对过去开发软件的兼容性。

@SuppressWarnings

先看一下下面的程序段。

上面这个简短的小程序,编译及运行是没有任何问题的。但Eclipse会对第05行提出警告提示信息: “The value of the local variable i is not used(局部变量i没有被使用)”,如下图所示。

有没有遇到过这样的情况:明明这个问题是你知道的,Eclipse还是不停地提示你,是不是很烦?而这个时候如果不想让其显示的话,就可以使用@SuppressWarnings 的注解压制警告的信息。

演示@ SuppressWarnings Annotation的效果(SuppressWarningsAnnotation.java)。


第06行使用了@SuppressWarnings(“unused”),这样第07行的局部变量i虽然声明了没有使用,也不会弹出警告信息。注解@SuppressWarnings 用于有选择地关闭编译器对类、方法、成员变量、变量初始化的警告。

自定义Annotation

之前已经介绍了3种Java中内置的注解,只要通过固定的格式调用即可。下面我们学习一下扩展类型的注解——自定义Annotation。

自定义Annotation的语法如下。

要自定义注解,需要使用@interface的方式进行定义,但是从上面的格式可以发现,在定义注解时也可以定义各种变量,但是变量定义之后必须使用括号()。

提示
使用@interface就相当于继承了Annotation接口。在程序中只要使用了@interface声明Annotation,那么此Annotation实际上就相当于继承了java.lang.annotation.Annotation接口。

演示自定义Annotation的效果(Test.java)。


第01~03行定义了一个MyAnnotation Annotation,其中没有定义任何的变量,这里仅仅给出一个演示框架。第08行演示了使用自定义的MyAnnotation。

当然在自定义的Annotation中也可以定义变量。参见下面的例子。

演示在自定义Annotation中定义变量(TestAnnoVar.java)。


第01~05行定义了一个AnnotationVar Annotation,其中声明了key和value两个变量。

第8行使用“变量名 = 值”的形式为自定义Annotation中的变量赋值。另外需要注意的是,如果在自定义Annotation中声明变量,且没有设置默认值,则在使用该Annotation时必须为变量赋值。下面演示一下为自定义Annotaion中的变量设置为默认值的例子。

演示为自定义Annotation中变量设置默认值(TestVarDefault.java)。


第01~05行定义了一个AnnotationVar Annotation,其中声明了key和value两个变量,并分别设置了默认值。第8行使用该自定义Annotation并且没有为变量赋值,则编译器会自动使用默认值为变量赋值

Annotation中变量的内容可以通过枚举指定范围。参见下面的程序。

演示在自定义Annotation中使用枚举(TestAnnotationEnum.java)。

Retention和RetentionPolicy

在注解中,可以使用Retention(中文含义为“保留”)来定义一个Annotation的保存范围。它的定义如下。

其中,@Documented有注解的作用,将自定义注解设置为文档说明信息

RetentionPolicy是一个枚举类型,用于指定Annotation的保存范围。RetentionPolicy包含3个取值范围:

⑴ SOURCE : 此Annotation类型的信息只会记录在源文件中,编译时将被编译器丢弃,及此Annotation信息不会保存在编译好的类文件中。

⑵ CLASS : 编译器将把注释记录在类文件中,但不会被加载到JVM中。如果一个Annotation声明时没有指定范围,则系统默认值是CLASS。

⑶ RUNTIME : 此Annotation类型的信息将会保留在源文件、类文件中,在执行时也会加载到Java虚拟机(JVM)中,因此可以反射性地读取。

前面文章里的3个内建的Annotation的保存范围分别为:

⑴ Override定义采用的是@Retention( value = SOURCE ),注解信息只能在源文件中出现。

⑵ Deprecated定义采用的是@Retention( value = RUNTIME ),注解信息在执行时出现。

⑶ SuppressWarnings定义采用的是@Retention( value = SOURCE ), 注解信息只能在源文件中出现。

演示自定义Annotation的保存范围(RetentionAnnotation.java)。

代码第04行,Retention的value值被设置为RUNTIME(运行时),RetentionAnnotation的注解信息会保留在源文件、类文件及Java虚拟机中。

反射与Annotation

注解若想发挥更大作用,还需借反射机制之力。通过反射,可以取得在一个方法上声明的注解的全部内容。

在Filed、Method、Constructor的父类上定义了以下与Annotation反射操作相关的方法。

⑴ 取得全部的Annotation;

⑵ 判断操作的是否是指定的Annotation。

取得全部的Annotation

演示使用反射取得全部的Annotation(GetAnnotations. java)。



第05~07行给toString()方法加了3个内建Annotation。

第22~25行获得toString方法上的所有Annotation。但是,3个内建的Annotation中只有@Deprecated是RUNTIME类型。所以只输出了Deprecated。也就是说,只有定义采用的是@Retention( value = RUNTIME )的Annotation才能在程序运行时被反射机制取得。

取得指定的Annotation

上面的内容演示了如何取得全部的Annotation,现在我们学习一下如何取得指定的Annotation。

使用反射取得指定的Annotation(GetAnnotation.java)



第04~08行,定义了一个自定义的注解MyAnnotation。

第11~14行给toString()方法定义了4个注解:Override(覆写),Deprecated(过时), SuppressWarnings(压制警告),MyAnnotation(自定义注解)。

第24行判断toString方法上是否有指定的注解(也就是MyAnnotation)。

第27~34行获得该注解,并取得其中的变量值,然后输出。

深入Annotation

Target

如果一个Annotation没有明确地指明使用的位置,则可以在任意的位置使用,例如之前所讲解的全部的Annotation因为没有指定应用位置,所以可以在任意的位置上使用。

那么,如果要让一个自定义的Annotation只能在指定的位置上使用,那么该怎么办呢?例如,只能在类上或方法上使用。这时可以使用@Target Annotation。

@Target Annotation明确地指出了一个Annotation的使用位置。在@TargetAnnotation中存在一个ElementType[] 枚举类型的变量,这个变量主要用于指定Annotation的使用限制。ElementType类定义了下面8个取值。

⑴ ANNOTATION_TYPE : 只能用在注释上;

⑵ CONSIRUCTOR : 只能用在构造方法的声明上;

⑶ FOELD : 只能用在字段的声明(包括枚举常量)上;

⑷ LOCAL_VARIABLE : 只能用在局部变量的声明上;

⑸ METHOD : 只能用在方法的声明上;

⑹ PACKAGE : 只能用在包的声明上;

⑺ PARAMEIER : 只能用在参数的声明上;

⑻ TYPE : 只能用在类、接口、枚举类型上。

下面范例演示了如何定义一个注解,此注解只能用在方法上,而不能用在其他地方。

限制自定义Annotation的使用范围(MyAnnotation.java)

第03行将@Target Annotation的value值设为METHOD,因此MyAnnotation只能在方法声明上使用,用在其他地方都会产生编译错误。

如果要为value设置多个值,例如,给自定义Annotation可设置在类以及方法上使用,可以使用下面的方法。

也就是使用数组的初始化方式。而如果value的值只取一个,可省略花括号"{ }"。

Documented注释

查看【范例 使用反射取得指定的Annotation(GetAnnotation.java)】GetAnnotation.java生成的Javadoc文档(为具有default权限的成员创建Javadoc),打开Info类的文档可以看到toString方法的详细资料。

可以发现@Deprecated Annotation出现在详细资料上,而 @Override、@SuppressWarnings和 @MyAnnotation却都没有,这是为什么呢?

其实这就是@Documented 注解的作用,它可将自定义的注解设置为文档说明信息,因此在生成Javadoc时就会将该注解加入文档,下面我们将【范例 使用反射取得指定的Annotation(GetAnnotation.java)】中的MyAnnotation稍微改一下,使MyAnnotation注解也可以出现在Javadoc文档中。

使用@Documented Annotation(MyAnnotation.java)。

再重新生成Javadoc文档,会发现MyAnnotation已经在文档中了。

Inherited

@Inherited用于标注一个父类的Annotation是否可以被子类继承,如果一个Annotation需要被其子类所继承,则在声明时直接使用@Inherited注释即可。如果没有写上此注释,则此Annotation无法被继承的。

使用@Inherited Annotation(InheritedAnnotation.java)

虽然Student类上并没有直接使用InheritedAnnotation Annotation,但是却从Person类上继承了下来,所以可以通过反射机制取出该Annotation。请读者自行验证,这里就不在赘述了。

高手点拨

  1. Annotation是JDK1.5之后新增的功能,主要是使用注释的形式进行程序的开发,通常配合反射、枚举等机制使用。

  2. 一个Annotation要想配合反射机制就必须设置@Retention( value =RetentionPolicy. RUNTIME )。

  3. @Target Annotation可以指定一个Annotation的使用范围。

  4. 如果一个Annotation希望被使用类的子类所继承,则要使用@InheritedAnnotation。

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