A.1 注解

Java 8在两个方面对注解机制进行了改进,分别为:

  • 你现在可以定义重复注解;
  • 使用新版Java,你可以为任何类型添加注解。

正式开始介绍之前,先快速地回顾一下注解在Java 8之前的版本中能做什么,这有助于加深我们对新特性的理解。

Java中,注解是一种使用附加信息装饰程序元素的机制(注意,Java 8之前,只有声明可以被注解)。换句话说,它就像是一种语法元数据(syntactic metadata)。比如,JUnit框架中就用了非常多的注解。下面这段代码中,setUp方法使用了@Before进行注解,而testAlgorithm使用了@Test进行注解:

  1. @Before
  2. public void setUp(){
  3. this.list = new ArrayList<>();
  4. }
  5. @Test
  6. public void testAlgorithm(){
  7. ...
  8. assertEquals(5, list.size());
  9. }

注解尤其适用于下面这些场景。

  • 在JUnit的上下文中,使用注解能帮助区分哪些方法是真正的单元测试,哪些是在做环境搭建工作。
  • 注解可以用于文档编制。比如,@Deprecated注解被广泛应用于说明某个方法不再推荐使用。
  • Java编译器还可以依据注解检测错误,禁止报警输出,甚至还能生成代码。
  • 注解在Java企业版中尤其流行,它们经常被用于配置企业应用程序。

A.1.1 重复注解

老版的Java禁止对同一个声明使用多个同类的注解。由于这个原因,下面的第二行代码是无效的:

  1. @interface Author { String name(); }
  2. @Author(name="Raoul") @Author(name="Mario") @Author(name="Alan") ←---- 错误:重复的注解
  3. class Book{ }

Java企业版的程序员经常通过一些惯用法绕过这一限制。你可以声明一个新的注解,它包含了你希望重复的注解数组。这种方法的形式如下:

  1. @interface Author { String name(); }
  2. @interface Authors {
  3. Author[] value();
  4. }
  5. @Authors(
  6. { @Author(name="Raoul"), @Author(name="Mario") , @Author(name="Alan")}
  7. )
  8. class Book{}

Book类的嵌套注解看起来相当丑陋。这是Java 8想要彻底移除这一限制的原因,去掉这一限制后,代码的可读性会好很多。由于新版Java中规定允许重复注解,因此你现在可以毫无顾虑地在一个声明中使用多个同种类型的注解了。然而,目前这还不是默认行为,你需要显式地要求重复注解。

创建一个重复注解

如果一个注解在设计之初就是可重复的,那么你可以直接使用它。但是,如果你提供的注解是为用户提供的,那么就需要做一些工作,说明该注解可以重复。下面是你需要执行的两个步骤。

(1) 将注解标记为@Repeatable

(2) 提供一个注解的容器。

下面的例子展示了如何将@Author注解修改为可重复注解:

  1. @Repeatable(Authors.class)
  2. @interface Author { String name(); }
  3. @interface Authors {
  4. Author[] value();
  5. }

完成了这样的定义之后,Book类可以通过多个@Author注解进行注释,如下所示:

  1. @Author(name="Raoul") @Author(name="Mario") @Author(name="Alan")
  2. class Book{ }

编译时,Book会被认为使用了类似@Authors({@Author(name="Raoul"), @Author(name="Mario"), @Author(name="Alan")})这样的注解,所以,你可以把这种新机制看作一种语法糖,它提供了Java程序员之前采用的惯用法那样的功能。为了确保与反射方法在行为上的一致性,注解会被封装到一个容器中。Java API的getAnnotation(Class annotation-Class)方法会为注解元素返回类型为T的注解。如果实际情况有多个类型为T的注解,该方法返回的到底是哪一个呢?

我们不希望一下子就陷入细节的魔咒,类Class提供了一个新的getAnnotationsByType方法,它可以帮助我们更好地使用重复注解。比如,你可以像下面这样打印输出Book类的所有Author注解:

  1. public static void main(String[] args) {
  2. Author[] authors = Book.class.getAnnotationsByType(Author.class); ←---- 返回一个由重复注解组成的数组
  3. Arrays.asList(authors).forEach(a -> { System.out.println(a.name()); });
  4. }

这段代码要正常工作的话,需要确保重复注解及它的容器都有运行时保持策略。关于与遗留反射方法的兼容性的更多讨论,可以参考http://cr.openjdk.java.net/~abuckley/8misc.pdf

A.1.2 类型注解

从Java 8开始,注解已经能应用于任何类型。这其中包括new操作符、类型转换、instanceof检查、泛型类型参数,以及implementsthrows子句。这里举了一个例子,这个例子中类型为String的变量name不能为空,所以使用了@NonNull对其进行注解:

  1. @NonNull String name = person.getName();

类似地,你可以对列表中的元素类型进行注解:

  1. List<@NonNull Car> cars = new ArrayList<>();

为什么这么有趣呢?实际上,利用好对类型的注解非常有利于对程序进行分析。这两个例子中,通过这一工具可以确保getName不返回空,cars列表中的元素总是非空值。这会极大地帮助你减少代码中不期而至的错误。

Java 8并未提供官方的注解或者一种工具能以开箱即用的方式使用它们。它仅仅提供了一种功能,你使用它可以对不同的类型添加注解。幸运的是,这个世界上还存在一个名为Checker的框架,它定义了多种类型注解,使用它们你可以增强类型检查。如果对此感兴趣,建议你看看它的教程,地址链接为:http://www.checkerframework.org。关于在代码中的何处使用注解的更多内容,可以访问http://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.7.4