本附录中, 我们会讨论Java 8中其他的三个语言特性的更新, 分别是重复注解(repeated annotation)、类型注解(type annotation)和通用目标类型推断(generalized target-type inference)。 附录B会讨论Java 8中类库的更新。我们不会涉及JDK 8中的所有内容,比如我们不会谈及Nashorn 或者是精简运行时(Compact Profiles),因为它们属于JVM的新特性。本书专注于介绍类库和语 言的更新。
注解
Java 8在两个方面对注解机制进行了改进,分别为:
- 你现在可以定义重复注解
- 使用新版Java,你可以为任何类型添加注解
正式开始介绍之前,我们先快速地回顾一下Java 8之前的版本能用注解做什么,这有助于我 们加深对新特性的理解。 Java中的注解是一种对程序元素进行配置,提供附加信息的机制(注意,在Java 8之前,只 有声明可以被注解)。换句话说,它是某种形式的语法元数据(syntactic metadata)。比如,注解 在JUnit框架中就使用得非常频繁。下面这段代码中,setUp方法使用了@Before进行注解,而 testAlgorithm使用了@Test进行注解:
1
2
3
4
5
6
7
8
9
10
|
@Before
public void setUp() {
this.list = new ArrayList<>();
}
@Test
public void testAlgorithm() {
...
assertEquals(5, list.size());
}
|
注解尤其适用于下面这些场景。
- 在JUnit的上下文中,使用注解能帮助区分哪些方法用于单元测试,哪些用于做环境搭建 工作。
- 注解可以用于文档编制。比如,@Deprecated注解被广泛应用于说明某个方法不再推荐使用。
- Java编译器还可以依据注解检查错误,禁止报警输出,甚至还能生成代码。
- 注解在Java企业版中尤其流行,它们经常用于配置企业应用程序。
重复注解
之前版本的Java禁止对同样的注解类型声明多次。由于这个原因,下面的第二句代码是无 效的:
1
2
3
4
5
6
7
8
9
|
@interface Author {
String name();
}
@Author(name = "Raoul")
@Author(name = "Mario")
@Author(name = "Alan")
class Book {
}
|
Java企业版的程序员经常通过一些惯用法绕过这一限制。你可以声明一个新的注解,它包含 了你希望重复的注解数组。这种方法的形式如下:
1
2
3
4
5
6
7
8
|
@Authors(
{
@Author(name = "Raoul"),
@Author(name = "Mario"),
@Author(name = "Alan")}
)
class Book {
}
|
Book类的嵌套注解相当难看。这就是Java 8想要从根本上移除这一限制的原因,去掉这一限 制后,代码的可读性会好很多。现在,如果你的配置允许重复注解,你可以毫无顾虑地一次声明 多个同一种类型的注解。它目前还不是默认行为,你需要显式地要求进行重复注解。
创建一个重复注解
如果一个注解在设计之初就是可重复的,你可以直接使用它。但是,如果你提供的注解是为 用户提供的,那么就需要做一些工作,说明该注解可以重复。下面是你需要执行的两个步骤:
-
将注解标记为@Repeatable
-
提供一个注解的容器
下面的例子展示了如何将@Author注解修改为可重复注解:
1
2
3
4
5
6
7
8
|
@Repeatable(Authors.class)
@interface Author {
String name();
}
@interface Authors {
Author[] value();
}
|
完成了这样的定义之后,Book类可以通过多个@Author注解进行注释,如下所示:
1
2
3
4
5
|
@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注解:
1
2
3
4
5
6
7
8
9
10
11
|
class Client {
public static void main(String[] args) {
// 方式一 通过容器注解获取
Author[] authors = Book.class.getAnnotation(Authors.class).value();
System.out.println(Arrays.toString(authors));
// 方式二 通过元素注解获取
Author[] authors1 = Book.class.getAnnotationsByType(Author.class);
System.out.println(Arrays.toString(authors1));
}
}
|
这段代码要正常工作的话,需要确保重复注解及它的容器都有运行时保持策略。
完整代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Authors.class)
@interface Author {
String name();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Authors {
Author[] value();
}
@Author(name = "Raoul")
@Author(name = "Mario")
@Author(name = "Alan")
class Book {
}
class Client {
public static void main(String[] args) {
// 方式一 通过容器注解获取
Author[] authors = Book.class.getAnnotation(Authors.class).value();
System.out.println(Arrays.toString(authors));
// 方式二 通过元素注解获取
Author[] authors1 = Book.class.getAnnotationsByType(Author.class);
System.out.println(Arrays.toString(authors1));
}
}
|
类型注解
原文链接:https://www.jianshu.com/p/0a076ae4a11b
Lambda
表达式是迄今为止Java 8讨论和推广最多的特性。我同意Lambda
是一个很大的改进,但是我认为其他一些Java 8特性由于Lambda
而显得有点暗淡。在这篇文章中,我想展示另一个很好的Java 8特性的一些例子:类型注解
类型注解是一种可以放在任何使用类型的位置上的注释(注:在 Java 8 之前的版本中,只能在声明式前使用注解)。这包括new运算符,类型转换,implements
子句和throws
子句。类型注解加强了分析Java代码的能力,并能够确保更强大的类型检查。
在代码实现上,Java 8为注解增加了两个新的注解类型:
1
2
3
|
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
public @interface Test {
}
|
TYPE_PARAMETER
允许注解被用在类型参数(注:即泛型)的声明中(比如 MyClass<@Test T>
)。
TYPE_USE
允许注解可以被用在任何类型使用的地方。
请注意,在Java 8中,以下示例中的注解没有实际作用。Java 8仅提供定义这些类型的注释的功能,然后由框架和工具开发者来实际上使用它们。所以这是一个将来框架会提供给我们的一个注解集合。以下大多数示例都来自Type Annotations规范和各种Java 8演示文稿。
带有类型注解的简单类型定义如下所示:
1
2
3
|
@NotNull String str1 = ...
@Email String str2 = ...
@NotNull @NotBlank String str3 = ...
|
类型注解也可以用于嵌套类型
1
|
Map.@NonNull Entry = ...
|
用于构造函数
1
2
3
|
new @Interned MyObject()
new @NonEmpty @Readonly List<String>(myNonEmptyStringSet)
myObject.new @Readonly NestedClass()
|
类型转换
1
2
|
myString = (@NonNull String) myObject;
query = (@Untainted String) str;
|
继承
1
|
class UnmodifiableList<T> implements @Readonly List<T> { ... }
|
泛型
1
2
3
4
|
List<@Email String> emails = ...
List<@ReadOnly @Localized Message> messages = ...
Graph<@Directional Node> directedGraph = ...
Map<@NonNull String, @NonEmpty List<@Readonly Document>> documents;
|
包括参数边界和通配符边界
1
2
3
|
class Folder<F extends @Existing File> { ... }
Collection<? super @Existing File> c = ...
List<@Immutable ? extends Comparable<T>> unchangeable = ...
|
抛出异常
1
2
|
void monitorTemperature() throws @Critical TemperatureException { ... }
void authenticate() throws @Fatal @Logged AccessDeniedException { ... }
|
instanceof
语句
1
2
|
boolean isNonNull = myString instanceof @NonNull String;
boolean isNonBlankEmail = myString instanceof @NotBlank @Email String;
|
以及最后Java 8 的方法引用
1
2
3
|
@Vernal Date::getDay
List<@English String>::size
Arrays::<@NonNegative Integer>sort
|
小结
类型注解是Java类型系统的一个有趣的补充。它们可以应用于任何类型的使用,并启用更详细的代码分析。如果您现在要使用类型注解,您应该查看Checker Framework。
通用目标类型推断
Java 8对泛型参数的推断进行了增强。相信你对Java 8之前版本中的类型推断已经比较熟悉 了。比如,Java中的方法emptyList方法定义如下:
1
2
3
4
|
public class Collections {
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}
|
emptyList方法使用了类型参数T进行参数化。你可以像下面这样为该类型参数提供一个显 式的类型进行函数调用:
1
|
List<String> ss = Collections.<String>emptyList(); // 编译器会提示 可推断的显示类型
|
不过Java也可以推断泛型参数的类型。上面的代码和下面这段代码是等价的:
1
|
List<String> ss = Collections.emptyList();
|
Java 8出现之前,这种推断机制依赖于程序的上下文(即目标类型),具有一定的局限性。比 如,下面这种情况就不能完成推断:
1
2
3
4
5
6
|
public static void main(String[] args) {
t(Collections.emptyList()); // 编译器会提示错误:List<String> can not be applied to List<Object>
}
static void t(List<String> sss) {
}
|
为了修复这一问题,你只能像我们之前展示的那样提供一个显式的类型参数。 Java 8中,目标类型包括向方法传递的参数,因此你不再需要提供显式的泛型参数:
1
2
3
4
5
6
|
public static void main(String[] args) {
t(Collections.emptyList());
}
static void t(List<String> sss) {
}
|
这也是为什么lambda表达式和方法引用能够推断类型。
1
2
3
|
List<Car> cleanCars = dirtyCars.stream()
.filter(Car::isClean)
.collect(Collectors.toList());
|
通过这段代码, 我们能很清晰地了解到, 正是伴随Java 8而来的改进让你只需要一句 Collectors.toList()就能完成期望的工作,不再需要编写像Collectors.<Car>toList()
这么复杂的代码了。