结束关于注释的讨论之前(至少在本系列文章中),我想简要地讨论一下注释的注释。第 1 部分中所接触的预定义注释类型都有预定义的目的。但是在编写自己的注释类型时,注释类型的目的并不总是显而易见的。除了基本的文档外,可能还要针对某个特定的成员类型或者一组成员类型编写类型。这就要求您为注释类型提供某种元数据,以便编译器保证按照预期的目的使用注释。
当然,首先想到的就是 Java 语言选择的元数据形式 —— 注释。您可以使用 4 种预定义的注释类型(称为 元注释 )对您的注释进行注释。我将对这 4 种类型分别进行介绍。
最明显的元注释就是允许何种程序元素具有定义的注释类型。毫不奇怪,这种元注释被称为
Target
。但是在了解如何使用
Target
之前,您还需要认识另一个类,该类被称为
ElementType
,它实际上是一个枚举。这个枚举定义了注释类型可应用的不同程序元素。清单 9 给出了完整的
ElementType
枚举:
清单 9. ElementType 枚举
package java.lang.annotation; public enum ElementType { TYPE, // Class, interface, or enum (but not annotation) FIELD, // Field (including enumerated values) METHOD, // Method (does not include constructors) PARAMETER, // Method parameter CONSTRUCTOR, // Constructor LOCAL_VARIABLE, // Local variable or catch clause ANNOTATION_TYPE, // Annotation Types (meta-annotations) PACKAGE // Java package } |
清单 9 中的枚举值意义很明确,您自己可以分析其应用的目标(通过后面的注解)。使用
Target
元注释时,至少要提供这些枚举值中的一个并指出注释的注释可以应用的程序元素。清单 10 说明了
Target
的用法:
package com.oreilly.tiger.ch06; import java.lang.annotation.ElementType; import java.lang.annotation.Target; /** * Annotation type to indicate a task still needs to be completed */ @Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE}) public @interface TODO { String value(); } |
现在,Java 编译器将把
TODO
应用于类型、方法、构造函数和其他注释类型。这样有助于避免他人误用您的注释类型(或者最好的地方是,
您自己
也不会因为疲惫而误用它)。
下一个要用到的元注释是
Retention
。这个元注释和 Java 编译器处理注释的注释类型的方式有关。编译器有几种不同选择:
- 将注释保留在编译后的类文件中,并在第一次加载类时读取它。
- 将注释保留在编译后的类文件中,但是在运行时忽略它。
- 按照规定使用注释,但是并不将它保留到编译后的类文件中。
这三种选项用
java.lang.annotation.RetentionPolicy
枚举表示,如清单 11 所示:
package java.lang.annotation; public enum RetentionPolicy { SOURCE, // Annotation is discarded by the compiler CLASS, // Annotation is stored in the class file, but ignored by the VM RUNTIME // Annotation is stored in the class file and read by the VM } |
现在可以看出,
Retention
元注释类型使用清单 11 所示的枚举值中的一个作为惟一的参数。可以将该元注释用于您的注释,如清单 12 所示:
@Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { // annotation type body } |
如清单 12 所示,这里可以使用简写形式,因为
Retention
只有一个成员变量。如果要将保持性设为
RetentionPolicy.CLASS
,那么什么也不需要做,因为这就是默认行为。
下一个元注释是
Documented
。这个元注释也非常容易理解,部分原因是
Documented
是一个标记注释。您应该还记得第 1 部分中曾经提到,标记注释没有成员变量。
Documented
表示注释应该出现在类的 Javadoc 中。在默认情况下,注释
不
包括在 Javadoc 中,如果花费大量时间注释一个类、详细说明未完成的工作、正确完成了什么或者描述行为,那么您应该记住这一点。
清单 13 说明了
Documented
元注释的用法:
package com.oreilly.tiger.ch06;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Marker annotation to indicate that a method or class
* is still in progress.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface InProgress { }
|
Documented
的一个实用技巧是保持性策略。注意,清单 13 中规定注释的保持性(retention)是
RUNTIME
,这是使用
Documented
注释类型所
必需
的。Javadoc 使用虚拟机从其类文件(而非源文件)中加载信息。确保 VM 从这些类文件中获得生成 Javadoc 所需信息的惟一方法是将保持性规定为
RetentionPolicy.RUNTIME
。这样,注释就会保留在编译后的类文件中
并且
由虚拟机加载,然后 Javadoc 可以从中抽取出来添加到类的 HTML 文档中。
最后一个元注释
Inherited
,可能是最复杂、使用最少、也最容易造成混淆的一个。这就是说,我们简单地看一看就可以了。
首先考虑这样一种情况:假设您通过定制的
InProgress
注释标记一个类正在开发之中,这完全没有问题,对吧?这些信息还会出现在 Javadoc 中,只要您正确地应用了
Documented
元注释。现在,假设您要编写一个新类,扩展那个还在开发之中的类,也不难,是不是?但是要记住,那个超类还在开发之中。如果您使用子类,或者查看它的文档,根本没有线索表明还有什么地方没有完成。您本来希望看到
InProgress
注释被带到子类中 —— 因为这是
继承
的 —— 但情况并非如此。您必须使用
Inherited
元注释说明所期望的行为,如清单 14 所示:
package com.oreilly.tiger.ch06;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Marker annotation to indicate that a method or class
* is still in progress.
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface InProgress { }
|
添加
@Inherited
后,您将看到
InProgress
出现在注释类的子类中。当然,您并不希望所有的注释类型都具有这种行为(因此默认值是
不
继承的)。比如,
TODO
注释就不会(也不应该)被传播。但是对于这里示范的情况,
Inherited
可能非常有用。
|
|
现在,您也许已经准备回到 Java 世界为所有的事物编写文档和注释了。这不禁令我回想起人们了解 Javadoc 之后发生的事情。我们都陷入了文档过滥的泥潭,直到有人认识到最好使用 Javadoc 来理清容易混淆的类或者方法。无论用 Javadoc 做了多少文章,也没有人会去看那些易于理解的
getXXX()
和
setXXX()
方法。
注释也可能出现同样的趋势,虽然不一定到那种程度。经常甚至频繁地使用标准注释类型是一种较好的做法。所有的 Java 5 编译器都支持它们,它们的行为也很容易理解。但是,如果要使用定制注释和元注释,那么就很难保证花费很大力气创建的那些类型在您的开发环境之外还有什么意义。因此要慎重。在合理的情况下使用注释,不要荒谬使用。无论如何,注释都是一种很好的工具,可以在开发过程中提供真正的帮助。