注释,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 的书籍的“注释”一章中的代码示例(请参阅 参考资料)。
元数据的价值
|
编译器检查
|
代码分析
Object
,但方法可能仅使用
Integer
。这在好些情况下很容易发生,比如在方法被覆盖而超类使用常规参数声明方法时,还有正在进行许多序列化的系统中也容易发生。在这两种情况中,元数据可以指示代码分析工具,虽然参数类型是
Object
,但
Integer
才是真正需要的。这类分析非常有用,但也不能夸大它的价值。
在 更复杂的情况下,代码分析工具可以执行所有种类的额外任务。示例 du jour 是 Enterprise JavaBean (EJB) 组件。甚至简单 EJB 系统中的依赖性和复杂性都非常令人吃惊。您具有了 home 接口和远程接口,以及本地接口和本地 home 接口,还有一个实现类。保持所有这些类同步非常困难。但是,元数据可以提供这个问题的解决放案。好的工具(还是要提一下 XDoclet)可以管理所有这些依赖性,并确保无“代码级”连接、但有“逻辑级”捆绑的类保持同步。元数据在这里确实可以发挥它的作用。
|
注释的基本知识
@
),后面是注释名称。然后在需要数据时,通过
name=value
对向注释提供数据。每次使用这类表示法时,就是在生成注释。一段代码可能会有 10 个、50 个或更多的注释。不过,您将发现多个注释都可能使用相同的
注释类型。类型是实际使用的结构,在特定上下文中,注释本身是该类型的具体使用(请参阅侧栏 注释或注释类型?)。
|
注释分为三个基本种类:
@MarkerAnnotation
是标记注释。它不包含数据,仅有注释名称。 @SingleValueAnnotation("my data")
。除了 @
标记外,这应该与普通的 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 |
清单 1 中的示例并没有乍一看那样复杂。 TODOItems
注释类型有一个具有值的变量。这里提供的值比较复杂,但 TODOItems
的使用实际与单一值注释类型相符,只是这里的单一值是数组而已。该数组包含三个 TODO
注释,其中每个注释都是多值的。逗号分隔每个注释内的值,以及单个数组内的值。非常容易,是吧?
但是我讲的可能超前了些。 TODOItems
和 TODO
是 定制注释, 是本系列文章第 2 部分中的主题。但是我想让您看到,即使复杂注释(清单 1 几乎是最复杂的注释)也不是非常令人害怕的。当提到 Java 语言的标准注释类型时,将很少看到如此复杂的情况。正如将在下面三个部分了解到的,Tiger 的基本注释类型的使用都极其简单。
|
Override 注释
Override
。
Override
应该仅用于方法(不用于类、包声明或其他构造)。它指明注释的方法将覆盖超类中的方法。清单 2 显示了简单的示例。
清单 2. 操作中的 Override 注释
package com.oreilly.tiger.ch06; |
清单 2 应该很容易理解。 @Override
注释对两个方法进行了注释 — toString()
和 hashCode()
,来指明它们覆盖 OverrideTester
类的超类 ( java.lang.Object
) 中的方法的版本。开始这可能看起来没什么作用,但它实际上是非常好的功能。如果不覆盖这些方法,根本 无法 编译此类。该注释还确保当您将 toString()
弄乱时,至少还有某种指示,即应该确保 hashCode()
仍旧匹配。
当编码到很晚且输错了某些东西时,此注释类型真的可以发挥很大的作用,如清单 3 中所示。
清单 3. 使 Override 注释捕获打字稿 package com.oreilly.tiger.ch06; |
在清单 3 中, hashCode()
错误地输入为 hasCode()
。注释指明 hasCode()
应该覆盖方法。但是在编译中, javac
将发现超类 ( java.lang.Object
) 没有名为 hasCode()
的方法可以覆盖。因此,编译器将报错,如图 1 中所示。
|
这个便捷的小功能将帮助快速捕获打字稿。
|
Deprecated 注释
Deprecated
。与
Override
一样,
Deprecated
是标记注释。正如您可能期望的,可以使用
Deprecated
来对不应再使用的方法进行注释。与
Override
不一样的是,
Deprecated
应该与正在声明为过时的方法放在同一行中(为什么这样?说实话我也不知道),如清单 4 中所示。
清单 4. 使用 Deprecated 注释
package com.oreilly.tiger.ch06; |
单独编译此类时,不会发生任何不同。但是如果通过覆盖或调用来使用声明为过时的方法,编译器将处理注释,发现不应该使用该方法,并发出错误消息,如图 2 中所示。
注意需要开启编译器警告,就像是必须向 Java 编译器指明想要普通的声明为过时警告。可以使用下列两个标记之一和 javac
命令: -deprecated
或新的 -Xlint:deprecated
标记。
|
SuppressWarnings 注释
SuppressWarnings
。发现该类型的作用应该不难,但是
为什么该注释类型如此重要通常不是很明显。它实际上是 Tiger 的所有新功能的副功能。例如,以泛型为例;泛型使所有种类的新类型安全操作成为可能,特别是当涉及 Java 集合时。然而,因为泛型,当使用集合而
没有 类型安全时,编译器将抛出警告。这对于针对 Tiger 的代码有帮助,但它使得为 Java 1.4.x 或更早版本编写代码非常麻烦。将不断地收到关于根本无关的事情的警告。如何才能使编译器不给您增添麻烦?
SupressWarnings
可以解决这个问题。 SupressWarnings
与 Override
和 Deprecated
不同, 是具有变量的 — 所以您将单一注释类型与该变量一起使用。可以以值数组来提供变量,其中每个值指明要阻止的一种特定警告类型。请看清单 5 中的示例,这是 Tiger 中通常会产生错误的一些代码。
public void nonGenericsMethod() { |
图 3 显示了清单 5 中代码的编译结果。
清单 6 通过使用 SuppressWarnings
注释消除了这种问题。
@SuppressWarings(value={"unchecked"}) |
非常简单,是吧?仅需要找到警告类型(图 3 中显示为“unchecked”),并将其传送到 SuppressWarnings
中。
SuppressWarnings
中变量的值采用数组,使您可以在同一注释中阻止多个警告。例如, @SuppressWarnings(value={"unchecked", "fallthrough"})
使用两个值的数组。此功能为处理错误提供了非常灵活的方法,无需进行大量的工作。