Annotation注释 (二)——java学习笔记

来源:互联网 发布:从零开始学淘宝开店 编辑:程序博客网 时间:2024/05/18 01:51

特此声明:
以下内容大部分来自于疯狂Java一书和廖雪峰老师的教程,如需转载请注明这两个出处,本文仅供自身学习,查缺补漏之用。


本文将会从两个角度来讲剩余的注解部分:

  • 元注解
  • 自定义Annotation

元注解

除了上篇说的 java.lang提供了5个基本的Annotation之外,还在java.lang.annotation包下提供了6个Meta Annotation(元Annotation),其中有五个元Annotation都用于修饰其他的Annotation定义。其中@Repeatable专门用于定义Java 8 新增的重复注解。

使用@Repeatable

@Repeatable只能用于修饰Annotation定义,用于指定被修饰的Annotation可以保留多长时间,@Repeatable
包含一个RetentionPolicy类型的value成员变量,所以使用@Repeatable时必须为该value成员变量指定值。

value成员变量的值只能是如下三个。

  • RetentionPolicy.CLASS,编译器讲把Annotation记录在class文件中。当运行java程序时,jvm不可获取annotation信息。这是默认值
  • RetentionPolicy.RUNTIME:编译器将把Annotation记录在class文件中,当运行java程序时,jvm也可以获取annotation信息,程序可以通过反射获取该Annotation信息
    -RetentionPolicy.SOURCE:Annotation只保留在源代码中,编译器直接丢弃这种Annotation。

如果需要通过反射获取朱姐信息,就需要使用value属性为RetentionPolicy.RUNTIME的@Retention。使用@Retention元Annotation可以采用如下代码为value指定值。

也可以采用如下代码来为value指定值

还有一点要注意, 如果当Annotation的成员便令名为value时,括号中直接指定的值就是默认给value的值。
具体的总结一下就是:如果使用注解时只需要为value成员变量指定值,则使用该注解时可以直接在该注解后的括号里指定value成员变量的值,无须使用“value=变量值”的形式

使用@Target

@Target也只能修饰一个Annotation定义,它用于指定被修饰的Annotation能用于修饰哪些程序单元。@Target元Annotation也包含一个名为value的成员变量,该成员变量的值只能是如下几个。

  • ElementType.ANNOTATION_TYPE:指定该策略的Annotation只能修饰Annotation
  • ElementType.CONSTRUCTOR:指定该策略的Annotation只能修饰构造器
  • ElementTypeFIELD:指定该策略的Annotation只能修饰成员变量
  • ElementType.LOCAL_VARIABLE:指定该策略的Annotation只能修饰局部变量
  • ElementType.METHOD:指定该策略的Annotation只能修饰方法定义
  • ElementType.PACKAGE:指定该策略的Annotation只能修饰包定义
  • ElementType.PARAMETER:指定该策略的Annotation只能修饰参数
  • ElementType.TYPE:指定该策略的Annotation只能修饰类、接口(包括注解类型)或枚举定义。

与使用@Retention类似的是,使用@Target也可以直接在括号里指定value值,而无需使用name=value的形式。如下代码指定@ActionListenerFor Annotation只能修饰成员变量。

@Target(ElementType.FIELD)public @interface ActionListenerFor{}

如下代码片段指定@Testable Annotation

@Target(ElementType.METHOD)public @interface Testable{}

使用@Documented

@Documented用于指定被该元Annotation修饰的Annotation类将被javadoc工具提取城文档,如果定义Annotation类时使用了@Documented修饰,则所有使用该Annotation修饰的程序元素的API文档中将会包含该Annotation说明

这部分可能不会很常用,所以不再过多赘述了,如果有需要的话请大家去谷歌或者必应。

使用@Inherited

@Inherited元Annotation指定被它修饰的Annotation将具有继承性——如果某个类使用了@xxx注解(定义该Annotation时使用了@Inherited修饰)修饰,则其子类将自动被@xxx修饰。

下面使用@Inherited元Annotation修饰@Inheritable定义,则该Annotation将具有继承性


上面程序的第三行代码表明@Inheritable将具有继承性,如果某个类使用了@Inherited修饰,则该自雷将自动使用@Inherited修饰。

下面程序中定义了一个Base基类,该基类使用了@Inheritable修饰,则Base类的子类将会默认使用@Inheritable修饰

-----

上面程序中的Base类使用了@Inheritable修饰,而该Annotation具有继承性,所以其自雷也将自动使用@Inheritable修饰。运行上面程序,会看到输出:true。

如果将InheritableTest.java程序中的粗体字代码注释掉或者删除,将会导致@Inheritable不具有继承性。运行上面程序,将看到输出:false。

自定义Annotation

下面我们看看如何利用上面的元注解来自己定义一些Annotation来完成一些实际的功能。

定义Annotation

定义新的Annotation类型使用@interface关键字(在原有的interface关键字前增加@符号)定义了一个新的Annotation类型与定义一个借口非常像,如下代码可以定义一个简单的Annotation类型

//定义一个简单的Annotation类型public @interface Test{}

定义了该Annotation之后,就可以在程序的任何地方使用该Annotation,使用Annotation的语法非常类似于public,和final这样的修饰符,通常可用于修饰程序中的类、方法、变量、接口等定义。通常把Annotation放在所有修饰符之前,而且由于使用Annotation时可能还需要为成员变量指定值,因而Annotation的长度可能较长,所以通产把Annotation另放一行,如下所示

//使用@Test修饰类定义@Testpublic class MyClass{...}

这样看起来也比较美观,不是吗

在默认情况下,Annotation可用于修饰任何程序元素,包括类、接口、方法等,如下程序使用@Test来修饰方法。

public class MyClass{//使用@Test Annotation修饰方法@Testpublic void info(){...}}

Annotation不仅可以是这种简单的Annotation,还可以带成员变量,Annotation的成员变量在Annotation定义中以无形参的方法形式来生命,其方法名与返回值定义了该成员变量的名字和类型。如下代码可以定义一个有成员变量的Annotation。

public @interface MyTag{//定义带两个成员变量的Annotation//Annotation中的成员变量以方法的形式来定义String name();int age();}

可能有人看出,上面定义Annotation的代码与定义接口的语法非常像,只是MyTag使用@interface关键字来定义,而接口使用interface,少了个@而已。

使用@interface定义的Annotation的确非常像定义了一个注解接口,这个注解接口集成了Annotation接口,这一点可以通过反射看到MyTag接口里包含了Annotation接口里的方法。

一旦在Annotation里定义了成员变量之后,使用该Annotation时就应该为该Annotation的成员变量指定值,如下代码所示。

public class Test{//使用带成员变量的Annotation时,需要为成员变量复制@MyTag(name = "xx",age=6)public void info(){...}}

也可以定义Annotation的成员变量时为其指定初始值(默认值),指定成员变量的初始值可使用default关键字,如下代码定义了@MyTag Annotation,该Annotation里包含了两个成员变量:name和age,这两个成员变量使用default指定了初始值

public @interface MyTag{//定义了两个成员变量的Annotation//使用default为两个成员变量指定初始值String name() default "yeeku";int age() default 32;}

如果为Annotation的成员变量指定了默认值,使用该Annotation时则可以不为这些成员变量指定值而是直接使用默认值

public class Test{//使用带成员变量的Annotation//因为它的成员变量有默认值,所以可以不为它的成员变量指定值@MyTagpublic void info(){...}...}

当然也可以在使用MyTag Annotation时为成员变量指定值,如果为MyTag的成员变量指定了值,则默认值不会起作用。

根据Annotation是否可以包含成员变量,可以把Annotation分为如下两类。

  • 标记Annotation:没有定义成员变量的Annotation类型被称为标记。这种Annotation仅利用自身的存在与否来提供信息,如前面介绍的@Override、@Test等Annotation。
  • 元数据Annotation:包含成员变量的Annotation,因为它们可以接受更多的元数据,所以也被称为元数据Annotation。

提取Annotation

使用Annotation修饰了类、方法、成员变量等成员之后,这些Annotation不会自己生效,必须由开发者提供相应的工具来提取并处理Annotation信息。

java使用Annotation接口来代表程序元素前面的注解,该接口是所有注解的负借口。Java 5 在java.lang.reflect包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素。该接口主要有一下几个实现类。

  • Class: 类定义
  • Constructor:构造器定义
  • Field:类的成员变量定义
  • Method:类的方法定义
  • Package:类的包定义。

接下来的内容感觉不是很重要,所以也就没有继续写下去,具体如果大家感兴趣可以参考疯狂Java讲义P650

使用Annotation的示例

下面分别介绍两个使用Annotation的例子,第一个Annotation Testable没有任何成员变量,仅是一个标记Annotation,它的作用是标记哪些方法是可测试的。
这里写图片描述

上面程序定义了一个@Testable Annotation ,定义该Annotation时使用了@Retention和@Target两个JDK的元注解,其中@Retention注解指定Testable注解可以保留到运行时(JVM可以提取到该Annotation的信息),而@Target注解指定@Testable只能修饰方法

上面的@Testable用于标记那些方法是可测试的,该Annotation可以作为JUnit测试框架的补充,在Junit框架中它要求测试用例的测试方法必须以test开头。如果使用@Testable注解,则可把任何方法标记为可测试的

如下MyTest测试用例中定义了8个方法,这8个方法没有太大区别,其中4个方法使用@Testable注解来标记这些方法是可测试的。
1111

正如前面提到的,仅仅使用注解来标记程序元素对程序时不会有任何影响的,这也是java注解的一条重要原则。为了让程序中的这些注解起作用,接下来必须为这些注解提供一个注解处理工具。

下面的注解处理工具会分析目标类,如果目标类中的方法使用了@Testable注解修饰,则通过反射来运行该测试方法。

11111

ProcessorTest类里只包含了一个process(String clazz)方法,该方法只可以接受一个字符串参数,该方法将会分析clazz参数所代表的类,并运行该类里使用@Testable修饰的方法。

该程序的主类非常简单,提供主方法,使用ProcessorTest来分析目标类即可。

---

运行上面程序,会看到如下运行结果:

----

通过这个运行结果可以看出,程序中的@Testable起作用了,MyTest类里以@Testable注解修饰的方法都被测试了。

通过上面例子堵着不难看出,其实Annotation十分简单,它是对源代码增加的一些特殊标记,这些特殊标记可以公国反射获取,当程序获取这些特殊标记后,程序可以做出相应的处理(当然也可以完全忽略这些Annotation)

前面介绍的只是一个标记Annotation,程序通过判断该Annotation是否存在来决定是否运行制定方法。下面程序通过使用Annotation来简化时间变成,在传统的时间变成中总是通过addActionListener()方法来为时间源绑定事件监听器,本实例程序中则通过@ActionListenerFor来为程序中的按钮绑定事件监听器。

----

定义了这个@ActionListenerFor之后,使用该注解时需要制定一个listener成员变量,该成员变量用于制定监听器的实现类。下面程序使用@ActionListenerFor注解来为两个按钮绑定时间监听器。

----

上面程序中的确定和取消定义了两个JButton按钮,并使用@ActionListenerFor注解为这两个按钮绑定了时间监听器,使用@ActionListenerFor注解时传入了listener元数据,该数据用于设定每个按钮的监听器实现类。

正如前面提到的,如果仅在程序中使用注解时起不到任何作用的,必须使用注解处理工具来处理程序中的注解。程序中的①处代码使用了ActionListenerInstaller类来处理本程序中的注解,该处理器分析目标对象中的所有成员变量,如果该成员变量前使用了@ActionListenerFor修饰,则取出该Annotation中的listener元数据,并根据该数据来绑定事件监听器。

----

上面程序中的粗体字代码根据@ActionListenerFor注解的元数据取得了监听器实现类,然后通过反射来创建监听器对象,接下来将监听器对象绑定到指定的按钮(按钮由被@ActionListenerFor修饰的Field表示)

运行上面的AnnotationTest程序,会看到如下所示的窗口

---

这里写图片描述
单一“确定”按钮,将会弹出如图所示的“单击了确认按钮”对话框,这表明使用该注解成功地为ok、cancel两个按钮绑定了事件监听器。

原创粉丝点击