【转】 Tiger 中的注释

转自:http://www.ibm.com/developerworks/cn/java/j-annotate1/index.html

 

第 1 部分: 向 Java 代码中添加元数据

如何使用 Java 5 的内置注释

 
 
 
 
 


级别: 初级

Brett McLaughlin (brett@newInstance.com), 作者/编者, O'Reilly Media, Inc

2004 年 9 月 01 日

 

注释,J2SE 5.0 (Tiger) 中的新功能,将非常需要的元数据工具引入核心 Java 语言。该系列文章分为两部分,在这第 1 部分中,作者 Brett McLaughlin 解释了元数据如此有用的原因,向您介绍了 Java 语言中的注释,并研究了 Tiger 的内置注释。

编程的一个最新的趋势,尤其是在 Java 编程方面,是使用 元数据。简单地说,元数据就是 关于数据的数据。元数据可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。许多元数据工具,如 XDoclet(请参阅 参考资料),将这些功能添加到核心 Java 语言中,暂时成为 Java 编程功能的一部分。

直到可以使用 J2SE 5.0(也叫做 Tiger,现在是第二个 beta 版本),核心 Java 语言才最接近具有 Javadoc 方法的元数据工具。您使用特殊的标签集合来标记代码,并执行 javadoc 命令来将这些标签转化成格式化的 HTML 页面,该页面说明标签所附加到的类。然而,Javadoc 是有缺陷的元数据工具,因为除了生成文档之外,您没有固定、实用、标准化的方式来将数据用于其他用途。HTML 代码经常混入到 Javadoc 输出中这一事实甚至更进一步降低了其用于任何其他目的的价值。

Tiger 通过名为 注释的新功能将一个更通用的元数据工具合并到核心 Java 语言中。注释是可以添加到代码中的修饰符,可以用于包声明、类型声明、构造函数、方法、字段、参数和变量。Tiger 包含内置注释,还支持您自己编写的定制注释。本文将概述元数据的优点并向您介绍 Tiger 的内置注释。本系列文章的 第 2 部分将研究定制注释。我要感谢 O'Reilly Media, Inc.,他们非常慷慨地 允许我在本文中使用我关于 Tiger 的书籍的“注释”一章中的代码示例(请参阅 参考资料)。

元数据的价值


一般来说,元数据的好处分为三类:文档编制、编译器检查和代码分析。代码级文档最常被引用。元数据提供了一种有用的方法来指明方法是否取决于其他方法,它们是否完整,特定类是否必须引用其他类,等等。这确实非常有用,但对于将元数据添加到 Java 语言中来说,文档编制可能是 最不相关的理由。Javadoc 已经提供了非常容易理解和健壮的方法来文档化代码。另外,当已经存在文档编制工具,并且在大多数时候都工作得很好时,谁还要编写文档编制工具?

不要漏掉本系列的另一部分

一定要阅读本系列的“ 第 2 部分”,该部分研究了定制注释。

编译器检查


元数据更重要的优点是编译器可以使用它来执行基本的编译时检查。例如,您将在本文后面的 Override 注释中看到 Tiger 引入了一个这样的注释,用于允许您指定一种方法覆盖超类中的另一种方法。Java 编译器可以确保在元数据中指明的行为实际发生在代码级别。如果从来没有找出过这种类型的 bug,这样做似乎有点傻,但是大多数年龄很大的 Java 编程老手都曾经花费至少多个晚上来查明他们的代码为什么不能用。当最后认识到方法的参数有错,且该方法实际上 没有 覆盖超类中的方法时,您可能更感到难受。使用元数据的工具有助于轻松地查明这种类型的错误,从而可以节省那些晚上来看长期进行的 Halo 联赛。

JSR 175

JSR 175, Java 编程语言的元数据工具,为将元数据合并到核心 Java 语言中提供了正式理由和说明(请参阅 参考资料)。根据 JSR,注释“不直接影响程序的语义。然而,开发和部署工具可以读取这些注释,并以某种形式处理这些注释,可能生成其他 Java 编程语言源文件、XML 文档或要与包含注释的程序一起使用的其他构件。”

代码分析


可以证明,任何好的注释或元数据工具的最好功能就是可以使用额外数据来分析代码。在一个简单的案例中,您可能构建代码目录,提供必需的输入类型并指明返回类型。但是,您可能想,Java 反射具有相同的优点;毕竟,可以为所有这些信息内省代码。这从表面上看似乎是正确的,但是在实际中通常不使用。许多时候,方法作为输入接受的或者作为输出返回的类型实际上不是该方法想要的类型。例如,参数类型可能是 Object ,但方法可能仅使用 Integer 。这在好些情况下很容易发生,比如在方法被覆盖而超类使用常规参数声明方法时,还有正在进行许多序列化的系统中也容易发生。在这两种情况中,元数据可以指示代码分析工具,虽然参数类型是 Object ,但 Integer 才是真正需要的。这类分析非常有用,但也不能夸大它的价值。

在更复杂的情况下,代码分析工具可以执行所有种类的额外任务。示例 du jour 是 Enterprise JavaBean (EJB) 组件。甚至简单 EJB 系统中的依赖性和复杂性都非常令人吃惊。您具有了 home 接口和远程接口,以及本地接口和本地 home 接口,还有一个实现类。保持所有这些类同步非常困难。但是,元数据可以提供这个问题的解决放案。好的工具(还是要提一下 XDoclet)可以管理所有这些依赖性,并确保无“代码级”连接、但有“逻辑级”捆绑的类保持同步。元数据在这里确实可以发挥它的作用。





回页首

注释的基本知识


现在已经了解了元数据的好处,我将介绍 Tiger 中的注释。注释采用“at”标记形式 ( @ ),后面是注释名称。然后在需要数据时,通过 name=value 对向注释提供数据。每次使用这类表示法时,就是在生成注释。一段代码可能会有 10 个、50 个或更多的注释。不过,您将发现多个注释都可能使用相同的 注释类型。类型是实际使用的结构,在特定上下文中,注释本身是该类型的具体使用(请参阅侧栏 注释或注释类型?)。

注释或注释类型?

是否对什么是注释与什么是注释类型感到迷惑?了解这个的最简单方法就是对比所熟悉的 Java 语言概念来想。可以定义一个类(例如 Person ),则在 JVM 中将总是仅有该类的一个版本(假设没有进行麻烦的类路径设置)。然而,在任何给定时间,可能会使用该类的 10 个或 20 个 实例。仍然是只有一个 Person 类,但是它以不同的方式使用多次。注释类型和注释也是这样。注释类型类似于类,注释类似于该类的实例。

注释分为三个基本种类:

  • 标记注释没有变量。注释显示简单,由名称标识,没有提供其他数据。例如, @MarkerAnnotation 是标记注释。它不包含数据,仅有注释名称。
  • 单一值注释与标记注释类似,但提供一段数据。因为仅提供很少的一点数据,所以可以使用快捷语法(假设注释类型接受此语法): @SingleValueAnnotation("my data") 。除了 @ 标记外,这应该与普通的 Java 方法调用很像。
  • 完整注释有多个数据成员。因此,必须使用更完整的语法(注释不再像普通的 Java 方法): @FullAnnotation(var1="data value 1", var2="data value 2", var3="data value 3")

除了通过默认语法向注释提供值外,还可以在需要传送多个值时使用名称-值对。还可以通过花括号为注释变量提供值数组。清单 1 显示了注释中的值数组的示例。

清单 1. 在注释中使用按数组排列的值

@TODOItems({    // Curly braces indicate an array of values is being supplied
  @TODO(
    severity=TODO.CRITICAL,
    item="Add functionality to calculate the mean of the student's grades",
    assignedTo="Brett McLaughlin"
  ),
  @TODO(
    severity=TODO.IMPOTANT,
    item="Print usage message to screen if no command-line flags specified",
    assignedTo="Brett McLaughlin"
  ),
  @TODO(
    severity=TODO.LOW,
    item="Roll a new website page with this class's new features",
    assignedTo="Jason Hunter"
  )
})

清单 1 中的示例并没有乍一看那样复杂。 TODOItems 注释类型有一个具有值的变量。这里提供的值比较复杂,但 TODOItems 的使用实际与单一值注释类型相符,只是这里的单一值是数组而已。该数组包含三个 TODO 注释,其中每个注释都是多值的。逗号分隔每个注释内的值,以及单个数组内的值。非常容易,是吧?

但是我讲的可能超前了些。 TODOItemsTODO定制注释,是本系列文章第 2 部分中的主题。但是我想让您看到,即使复杂注释(清单 1 几乎是最复杂的注释)也不是非常令人害怕的。当提到 Java 语言的标准注释类型时,将很少看到如此复杂的情况。正如将在下面三个部分了解到的,Tiger 的基本注释类型的使用都极其简单。





回页首

Override 注释


Tiger 的第一个内置注释类型是 OverrideOverride 应该仅用于方法(不用于类、包声明或其他构造)。它指明注释的方法将覆盖超类中的方法。清单 2 显示了简单的示例。 清单 2. 操作中的 Override 注释

package com.oreilly.tiger.ch06;
public class OverrideTester {
  public OverrideTester() { }
  @Override
  public String toString() {
    return super.toString() + " [Override Tester Implementation]";
  }
  @Override
  public int hashCode() {
    return toString().hashCode();
  }
}

清单 2 应该很容易理解。 @Override 注释对两个方法进行了注释 — toString()hashCode() ,来指明它们覆盖 OverrideTester 类的超类 ( java.lang.Object ) 中的方法的版本。开始这可能看起来没什么作用,但它实际上是非常好的功能。如果不覆盖这些方法,根本 无法 编译此类。该注释还确保当您将 toString() 弄乱时,至少还有某种指示,即应该确保 hashCode() 仍旧匹配。

当编码到很晚且输错了某些东西时,此注释类型真的可以发挥很大的作用,如清单 3 中所示。

清单 3. 使 Override 注释捕获打字稿

package com.oreilly.tiger.ch06;
public class OverrideTester {
  public OverrideTester() { }
  @Override
  public String toString() {
    return super.toString() + " [Override Tester Implementation]";
  }
  @Override
public int hasCode() {
    return toString().hashCode();
  }
}

在清单 3 中, hashCode() 错误地输入为 hasCode() 。注释指明 hasCode() 应该覆盖方法。但是在编译中, javac 将发现超类 ( java.lang.Object ) 没有名为 hasCode() 的方法可以覆盖。因此,编译器将报错,如图 1 中所示。

图 1. 来自 Override 注释的编译器警告

缺少的功能

在单一值注释类型中,如果 Deprecated 允许包含错误类型消息将更好。然后,当用户使用声明为过时的方法时,编译器可以打印消息。该消息可以指明使用方法的结果如何重要,说明何时将停止方法,甚至建议备用方法。可能 J2SE 的下一版本(“Mustang”,他们这样命名)将提供这种功能。

这个便捷的小功能将帮助快速捕获打字稿。





回页首

Deprecated 注释


下一个标准注释类型是 Deprecated 。与 Override 一样, Deprecated 是标记注释。正如您可能期望的,可以使用 Deprecated 来对不应再使用的方法进行注释。与 Override 不一样的是, Deprecated 应该与正在声明为过时的方法放在同一行中(为什么这样?说实话我也不知道),如清单 4 中所示。 清单 4. 使用 Deprecated 注释

package com.oreilly.tiger.ch06;
public class DeprecatedClass {
  @Deprecated public void doSomething() {
    // some code
  }
  public void doSomethingElse() {
    // This method presumably does what doSomething() does, but better
  }
}

单独编译此类时,不会发生任何不同。但是如果通过覆盖或调用来使用声明为过时的方法,编译器将处理注释,发现不应该使用该方法,并发出错误消息,如图 2 中所示。

图 2. 来自 Deprecated 注释的编译器警告

注意需要开启编译器警告,就像是必须向 Java 编译器指明想要普通的声明为过时警告。可以使用下列两个标记之一和 javac 命令: -deprecated 或新的 -Xlint:deprecated 标记。





回页首

SuppressWarnings 注释


从 Tiger “免费”获得的最后一个注释类型是 SuppressWarnings 。发现该类型的作用应该不难,但是 为什么该注释类型如此重要通常不是很明显。它实际上是 Tiger 的所有新功能的副功能。例如,以泛型为例;泛型使所有种类的新类型安全操作成为可能,特别是当涉及 Java 集合时。然而,因为泛型,当使用集合而 没有 类型安全时,编译器将抛出警告。这对于针对 Tiger 的代码有帮助,但它使得为 Java 1.4.x 或更早版本编写代码非常麻烦。将不断地收到关于根本无关的事情的警告。如何才能使编译器不给您增添麻烦?

SupressWarnings 可以解决这个问题。 SupressWarningsOverrideDeprecated 不同, 具有变量的 — 所以您将单一注释类型与该变量一起使用。可以以值数组来提供变量,其中每个值指明要阻止的一种特定警告类型。请看清单 5 中的示例,这是 Tiger 中通常会产生错误的一些代码。

清单 5. 不是类型安全的 Tiger 代码

public void nonGenericsMethod() {
  List wordList = new ArrayList();    // no typing information on the List
  wordList.add("foo");                // causes error on list addition
}

图 3 显示了清单 5 中代码的编译结果。

图 3. 来自非标准代码的编译器警告

清单 6 通过使用 SuppressWarnings 注释消除了这种问题。

清单 6. 阻止警告

@SuppressWarings(value={"unchecked"})
public void nonGenericsMethod() {
  List wordList = new ArrayList();    // no typing information on the List
  wordList.add("foo");                // causes error on list addition
}

非常简单,是吧?仅需要找到警告类型(图 3 中显示为“unchecked”),并将其传送到 SuppressWarnings 中。

SuppressWarnings 中变量的值采用数组,使您可以在同一注释中阻止多个警告。例如, @SuppressWarnings(value={"unchecked", "fallthrough"}) 使用两个值的数组。此功能为处理错误提供了非常灵活的方法,无需进行大量的工作。





回页首

结束语

虽然这里看到的语法可能都是新的,但您应该知道注释非常容易理解和使用。也就是说,与 Tiger 一起提供的标准注释相当简单,可以添加许多功能。元数据正日益变得有帮助,您肯定会提出非常适用于自己的应用程序的注释类型。在本系列文章的第 2 部分,我将详细说明 Tiger 对编写自己的注释类型的支持。您将了解如何创建 Java 类以及将其定义为注释类型,如何使编译器识别您的注释类型,以及如何使用该类型对代码进行注释。我甚至会更深入地说明奇异但有用的对注释进行注释的任务。您将快速熟悉 Tiger 中的这一新构造。

 

第 2 部分: 定制注释

本系列文章的 第 1 部分介绍了注释 —— J2SE 5.0 中新的元数据工具,并重点讨论了 Tiger 的基本内置注释。一个更强大的相关特性是支持编写自己的注释。本文中,Brett McLauglin 说明了如何创建定制注释,如何用自己的注释注解文档,并进一步定制代码。

本系列的第一篇文章 介绍了什么是元数据,元数据的重要性,以及如何使用 J2SE 5.0(也叫做 Tiger)的基本内置注释。如果习惯了这些概念,您可能已经在想,Java 5 提供的三种标准注释也并不是特别健壮,能使用的只有 DeprecatedSuppressWarningsOverride 而已。所幸的是,Tiger 还允许定义自己的注释类型。在本文中,我将通过一些示例引导您掌握这个相对简单的过程。您还将了解如何对自己的注释进行注解,以及这样做的一些好处。我要感谢 O'Reilly Media, Inc.,他们非常慷慨地允许我在本文中使用我关于 Tiger 的书籍的“注释”一章中的代码示例(请参阅 参考资料)。

定义自己的注释类型


通过添加了一个小小的语法(Tiger 添加了大量的语法结构),Java 语言支持一种新的类型 —— 注释类型(annotation type)。注释类型看起来很像普通的类,但是有一些特有的性质。最明显的一点是,可以在类中以符号( @ )的形式注释其他 Java 代码。我将一步一步地介绍这个过程。

@interface 声明


定义新的注释类型与创建接口有很多类似之处,只不过 interface 关键字之前要有一个 @ 符号。清单 1 中给出的是一个最简单的注释类型的示例:
清单 1. 非常简单的注释类型

package com.oreilly.tiger.ch06;
/**
 * Marker annotation to indicate that a method or class
 *   is still in progress.
 */
public @interface InProgress { }

 

清单 1 的含义非常明显。如果编译这个注释类型,并确信其位于类路径中,那么您就可以在自己的源代码方法中使用它,以指出某个方法或类仍在处理中,如清单 2 所示:

清单 2. 使用定制的注释类型

@com.oreilly.tiger.ch06.InProgress
public void calculateInterest(float amount, float rate) {
  // Need to finish this method later
}

 

清单 1 所示注释类型的使用方法和内置注释类型的使用方法完全相同,只不过要同时使用名称和所在的包来指示定制注释。当然,一般的 Java 规则仍然适用,您可以导入该注释类型,直接使用 @InProgress 引用它。

不要漏掉本系列的另一部分

一定要阅读本系列文章的“ 第 1 部分”,其中介绍了 Java 5.0 中的注释。

添加成员


上面所示的基本用法还远远不够健壮。您一定还记得“第 1 部分”中曾经提到的,注释类型可以有成员变量(请参阅 参考资料)。这一点非常有用,尤其是准备将注释作为更加复杂的元数据,而不仅仅将它作为原始文档使用的时候。代码分析工具喜欢加工大量的信息,定制注释可以提供这类信息。

注释类型中的数据成员被设置成使用有限的信息进行工作。定义数据成员后不需要分别定义访问和修改的方法。相反,只需要定义一个方法,以成员的名称命名它。数据类型应该是该方法返回值的类型。清单 3 是一个具体的示例,它澄清了一些比较含糊的要求:

清单 3. 向注释类型添加成员

package com.oreilly.tiger.ch06;
/**
 * Annotation type to indicate a task still needs to be
 *   completed.
 */
public @interface TODO {
  String value();
}

 

尽管清单 3 看起来很奇怪,但这是注释类型所要求的格式。清单 3 定义了一个名为 value 的字符串,该注释类型能够接受它。然后,就可以像清单 4 中那样使用注释类型:

清单 4. 使用带有成员值的注释类型

@com.oreilly.tiger.ch06.InProgress
@TODO("Figure out the amount of interest per month")
public void calculateInterest(float amount, float rate) {
  // Need to finish this method later
}

 

这里同样没有多少花样。清单 4 假设已经引入了 com.oreilly.tiger.ch06.TODO ,因此源代码中的注释 需要包名作前缀。此外,需要注意的是,清单 4 中采用了简写的方法:将值 ("Figure out the amount of interest per month") 直接提供给注释,没有指定成员变量名。清单 4 和清单 5 是等价的,后者没有采用简写形式:

清单 5. 清单 4 的“加长”版

@com.oreilly.tiger.ch06.InProgress
@TODO(value="Figure out the amount of interest per month")
public void calculateInterest(float amount, float rate) {
  // Need to finish this method later
}

 

当然作为编码人员,我们都不愿意跟这种“加长”版搅在一起。不过要注意,只有当注释类型只有 一个 成员变量,而且变量名为 value 时,才能使用简写形式。如果不符合这个条件,那么就无法利用这种特性。

设置默认值


迄今为止,您已经有了一个很好的起点,但是要做得完美,还有很长的一段路要走。您可能已经想到,下一步就要为注释设置某个默认值。如果您希望用户指定某些值,但是只有这些值与默认值不同的时候才需要指定其他的值,那么设置默认值就是一种很好的办法。清单 6 用另一个定制注释 —— 来自 清单 4TODO 注释类型的一个全功能版本,示范了这个概念及其实现:
清单 6. 带有默认值的注释类型

package com.oreilly.tiger.ch06;
public @interface GroupTODO {
  public enum Severity { CRITICAL, IMPORTANT, TRIVIAL, DOCUMENTATION };
  Severity severity() 
        default Severity.IMPORTANT;
  String item();
  String assignedTo();
  String dateAssigned();
}
      

 

清单 6 中的 GroupTODO 注释类型中添加了几个新的变量。因为该注释类型的成员变量不是一个,所以将一个变量命名为 value 没有任何意义。只要成员变量多于一个,就应该尽可能准确地为其命名。因为不可能从 清单 5所示的简写形式中获益,所以您需要创建虽然稍微有点冗长,但是更容易理解的注释类型。

清单 6 中出现的另一个新特性是注释类型定义了自己的枚举(枚举,即 enumeration,通常也称为 enums,是 Java 5 的另一个新特性。它并没有多么地不同凡响,对注释类型更是如此)。然后,清单 6 使用新定义的枚举作为一个成员变量的类型。

最后,再回到我们的主题 —— 默认值。建立默认值的过程非常琐碎,需要在成员声明的后面添加关键字 default ,然后提供默认值。正如您所料,默认值的类型必须与成员变量声明的类型完全相同。同样,这也不是什么火箭科学,只不过是词法上的变异。清单 7 给出了一个具体应用中的 GroupTODO 注释,其中 没有 指定 severity 成员:

清单 7. 使用默认值

  @com.oreilly.tiger.ch06.InProgress
  @GroupTODO(
    item="Figure out the amount of interest per month",
    assignedTo="Brett McLaughlin",
    dateAssigned="08/04/2004"
  )
  public  void calculateInterest(float amount, float rate) {
    // Need to finish this method later
  }

 

清单 8 中使用了同一个注释,但这一次给出了 severity 的值:

清单 8. 改写默认值

  @com.oreilly.tiger.ch06.InProgress
  @GroupTODO(
    severity=GroupTODO.Severity.DOCUMENTATION,
    item="Need to explain how this rather unusual method works",
    assignedTo="Jon Stevens",
    dateAssigned="07/30/2004"
  )
  public  void reallyConfusingMethod(int codePoint) {
    // Really weird code implementation
  }





回页首


对注释的注释


结束关于注释的讨论之前(至少在本系列文章中),我想简要地讨论一下注释的注释。第 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 的用法:

清单 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 所示:

清单 11. RetentionPolicy 枚举

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 所示:

清单 12. 使用 Retention 元注释

@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
  // annotation type body
}

 

如清单 12 所示,这里可以使用简写形式,因为 Retention 只有一个成员变量。如果要将保持性设为 RetentionPolicy.CLASS ,那么什么也不需要做,因为这就是默认行为。

添加公共文档


下一个元注释是 Documented 。这个元注释也非常容易理解,部分原因是 Documented 是一个标记注释。您应该还记得第 1 部分中曾经提到,标记注释没有成员变量。 Documented 表示注释应该出现在类的 Javadoc 中。在默认情况下,注释 包括在 Javadoc 中,如果花费大量时间注释一个类、详细说明未完成的工作、正确完成了什么或者描述行为,那么您应该记住这一点。

清单 13 说明了 Documented 元注释的用法:

清单 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 所示:

清单 14. 使用 Inherited 元注释

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 编译器都支持它们,它们的行为也很容易理解。但是,如果要使用定制注释和元注释,那么就很难保证花费很大力气创建的那些类型在您的开发环境之外还有什么意义。因此要慎重。在合理的情况下使用注释,不要荒谬使用。无论如何,注释都是一种很好的工具,可以在开发过程中提供真正的帮助。


参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文
  • 不要遗漏“ Tiger 中的注释,第 2 部分”,即本系列文章的第 2 部分,研究了定制注释。


  • 开放源代码 XDoclet代码生成引擎支持面向属性的 Java 语言编程。


  • JSR 175,将元数据工具合并到 Java 语言中的规范,处于 Java Community Process 的提议最终草案状态。


  • 访问 Sun 的主页,获取 J2SE 5.0 的所有信息


  • 可以 下载 Tiger并自己试用。


  • John Zukowski 的系列文章 Taming Tiger 以实用的基于技巧的形式讲述了 Java 5.0 的新功能。


  • 由 Brett McLaughlin 和 David Flanagan 撰写的 Java 1.5 Tiger: A Developer's Notebook 一书 (O'Reilly & Associates; 2004),以代码为中心、开发人员友好的形式,讲述了几乎所有的 Tiger 的最新功能 — 包括注释。


  • developerWorksJava 技术专区 可以找到数百篇有关 Java 技术的参考资料。


  • 访问 Developer Bookstore,获得技术书籍的完整列表,其中包括数百本 Java 相关主题的书籍。


  • 是否对无需通常的高成本入口点(entry point)或短期评估许可证的 IBM 测试产品感兴趣? developerWorks Subscription为 WebSphere?、DB2?、Lotus?、Rational? 和 Tivoli? 产品提供了低成本的 12 个月单用户许可证,包括基于 Eclipse 的 WebSphere Studio? IDE,用于开发、测试、评估和展示您的应用程序。


关于作者

作者照片

Brett McLaughlin 从 Logo 时代(还记得那个小三角吗?)就开始从事计算机工作。在最近几年里,他已经成为 Java 和 XML 社区最知名的作者和程序员之一。他曾经在 Nextel Communications 实现复杂的企业系统,在 Lutris Technologies 编写应用程序服务器,目前在为 O'Reilly Media, Inc 撰写和编辑 书籍

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值