本附录中,我们会讨论Java 8中其他的三个语言特性的更新,分别是重复注解(repeated annotation)、类型注解(type annotation)和通用目标类型推断(generalized target-type inference)。附录B会讨论Java 8中类库的更新。我们不会涉及JDK 8中的所有内容,比如我们不会谈及Nashorn或者是精简运行时(Compact Profiles),因为它们属于JVM的新特性。本书专注于介绍类库和语言的更新。如果你对Nashorm或者精简运行时感兴趣,我们推荐你阅读以下两个链接的内容,分别是http://openjdk.java.net/projects/nashorn/和http://openjdk.java.net/jeps/161。
A.1 注解
Java 8在两个方面对注解机制进行了改进,分别为:
你现在可以定义重复注解
使用新版Java,你可以为任何类型添加注解
正式开始介绍之前,我们先快速地回顾一下Java 8之前的版本能用注解做什么,这有助于我们加深对新特性的理解。
Java中的注解是一种对程序元素进行配置,提供附加信息的机制(注意,在Java 8之前,只有声明可以被注解)。换句话说,它是某种形式的语法元数据(syntactic metadata)。比如,注解在JUnit框架中就使用得非常频繁。下面这段代码中,setUp
方法使用了@Before
进行注解,而testAlgorithm
使用了@Test
进行注解:
@Beforepublic void setUp{ this.list = new ArrayList<>;}@Testpublic void testAlgorithm{ ... assertEquals(5, list.size);}
注解尤其适用于下面这些场景。
在JUnit的上下文中,使用注解能帮助区分哪些方法用于单元测试,哪些用于做环境搭建工作。
注解可以用于文档编制。比如,
@Deprecated
注解被广泛应用于说明某个方法不再推荐使用。Java编译器还可以依据注解检查错误,禁止报警输出,甚至还能生成代码。
注解在Java企业版中尤其流行,它们经常用于配置企业应用程序。
A.1.1 重复注解
之前版本的Java禁止对同样的注解类型声明多次。由于这个原因,下面的第二句代码是无效的:
@interface Author { String name; }@Author(name="Raoul") @Author(name="Mario") @Author(name="Alan") ←─错误:重复的注解class Book{ }
Java企业版的程序员经常通过一些惯用法绕过这一限制。你可以声明一个新的注解,它包含了你希望重复的注解数组。这种方法的形式如下:
@interface Author { String name; }@interface Authors { Author value;}@Authors( { @Author(name="Raoul"), @Author(name="Mario") , @Author(name="Alan")})class Book{}
Book
类的嵌套注解相当难看。这就是Java 8想要从根本上移除这一限制的原因,去掉这一限制后,代码的可读性会好很多。现在,如果你的配置允许重复注解,你可以毫无顾虑地一次声明多个同一种类型的注解。它目前还不是默认行为,你需要显式地要求进行重复注解。
创建一个重复注解
如果一个注解在设计之初就是可重复的,你可以直接使用它。但是,如果你提供的注解是为用户提供的,那么就需要做一些工作,说明该注解可以重复。下面是你需要执行的两个步骤:
(1) 将注解标记为@Repeatable
(2) 提供一个注解的容器
下面的例子展示了如何将@Author
注解修改为可重复注解:
@Repeatable(Authors.class)@interface Author { String name; }@interface Authors { Author value;}
完成了这样的定义之后,Book
类可以通过多个@Author
注解进行注释,如下所示:
@Author(name="Raoul") @Author(name="Mario") @Author(name="Alan")class Book{ }
编译时,Book
会被认为使用了@Authors({@Author(name="Raoul"), @Author(name ="Mario"), @Author(name="Alan")})
这样的形式进行了注解,所以,你可以把这种新的机制看成是一种语法糖,它提供了Java程序员之前利用的惯用法类似的功能。为了确保与反射方法在行为上的一致性,注解会被封装到一个容器中。Java API中的getAnnotation(Class<T> annotation-Class)
方法会为注解元素返回类型为 T
的注解。如果实际情况有多个类型为T
的注解,该方法的返回到底是哪一个呢?
我们不希望一下子就陷入细节的魔咒,类Class
提供了一个新的getAnnotationsByType
方法,它可以帮助我们更好地使用重复注解。比如,你可以像下面这样打印输出Book
类的所有Author
注解:
public static void main(String args) { Author authors = Book.class.getAnnotationsByType(Author.class); ←─返回一个由重复注解Author组成的数组 Arrays.asList(authors).forEach(a -> { System.out.println(a.name); });}
这段代码要正常工作的话,需要确保重复注解及它的容器都有运行时保持策略。关于与遗留反射方法的兼容性的更多讨论,可以参考http://cr.openjdk.java.net/~abuckley/8misc.pdf。
A.1.2 类型注解
从Java 8开始,注解已经能应用于任何类型。这其中包括new
操作符、类型转换、instanceof
检查、泛型类型参数,以及implements
和throws
子句。这里,我们举了一个例子,这个例子中类型为String
的变量name
不能为空,所以我们使用了@NonNull
对其进行注解:
@NonNull String name = person.getName;
类似地,你可以对列表中的元素类型进行注解:
List<@NonNull Car> cars = new ArrayList<>;
为什么这么有趣呢?实际上,利用好对类型的注解非常有利于我们对程序进行分析。这两个例子中,通过这一工具我们可以确保getName
不返回空,cars
列表中的元素总是非空值。这会极大地帮助你减少代码中不期而至的错误。
Java 8并未提供官方的注解或者一种工具能以开箱即用的方式使用它们。它仅仅提供了一种功能,你使用它可以对不同的类型添加注解。幸运的是,这个世界上还存在一个名为Checker的框架,它定义了多种类型注解,使用它们你可以增强类型检查。如果对此感兴趣,我们建议你看看它的教程,地址链接为:http://www.checker-framework.org。关于在代码中的何处使用注解的更多内容,可以访问http://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.7.4。
A.2 通用目标类型推断
Java 8对泛型参数的推断进行了增强。相信你对Java 8之前版本中的类型推断已经比较熟悉了。比如,Java中的方法emptyList
方法定义如下:
static <T> List<T> emptyList;
emptyList
方法使用了类型参数T
进行参数化。你可以像下面这样为该类型参数提供一个显式的类型进行函数调用:
List<Car> cars = Collections.<Car>emptyList;
不过Java也可以推断泛型参数的类型。上面的代码和下面这段代码是等价的:
List<Car> cars = Collections.emptyList;
Java 8出现之前,这种推断机制依赖于程序的上下文(即目标类型),具有一定的局限性。比如,下面这种情况就不大可能完成推断:
static void cleanCars(List<Car> cars) {}cleanCars(Collections.emptyList);
你会遭遇下面的错误:
cleanCars (java.util.List<Car>)cannot be applied to (java.util.List<java.lang.Object>)
为了修复这一问题,你只能像我们之前展示的那样提供一个显式的类型参数。
Java 8中,目标类型包括向方法传递的参数,因此你不再需要提供显式的泛型参数:
List<Car> cleanCars = dirtyCars.stream .filter(Car::isClean) .collect(Collectors.toList);
通过这段代码,我们能很清晰地了解到,正是伴随Java 8而来的改进让你只需要一句Collectors.toList
就能完成期望的工作,不再需要编写像Collectors.<Car>toList
这么复杂的代码了。