自从泛型被添加到 JDK 5 语言以来,它一直都是一个颇具争议的话题。一部 分人认为泛型简化了编程,扩展了类型系统从而使编译器能够检验类型安全;另 外一些人认为泛型添加了很多不必要的复杂性。对于泛型我们都经历过一些痛苦 的回忆,但毫无疑问通配符是最棘手的部分。
通配符基本介绍
泛型是一种表示类或方法行为对于未知类型的类型约束的方法,比如 “不管 这个方法的参数 x 和 y 是哪种类型,它们必须是相同的类型”,“必须为这些 方法提供同一类型的参数” 或者 “foo() 的返回值和 bar() 的参数是同一类 型的”。
通配符 — 使用一个奇怪的问号表示类型参数 — 是一种表示未知类型的类 型约束的方法。通配符并不包含在最初的泛型设计中(起源于 Generic Java (GJ)项目),从形成 JSR 14 到发布其最终版本之间的五年多时间内完成设计 过程并被添加到了泛型中。
通配符在类型系统中具有重要的意义,它们为一个泛型类所指定的类型集合 提供了一个有用的类型范围。对泛型类 ArrayList 而言,对于任意(引用)类 型 T,ArrayList<?> 类型是 ArrayList<T> 的超类型(类似原始 类型 ArrayList 和根类型 Object,但是这些超类型在执行类型推断方面不是很 有用)。
通配符类型 List<?> 与原始类型 List 和具体类型 List<Object> 都不相同。如果说变量 x 具有 List<?> 类型,这 表示存在一些 T 类型,其中 x 是 List<T>类型,x 具有相同的结构,尽 管我们不知道其元素的具体类型。这并不表示它可以具有任意内容,而是指我们 并不了解内容的类型限制是什么 — 但我们知道存在 某种限制。另一方面,原 始类型 List 是异构的,我们不能对其元素有任何类型限制,具体类型 List<Object> 表示我们明确地知道它能包含任何对象(当然,泛型的类 型系统没有 “列表内容” 的概念,但可以从 List 之类的集合类型轻松地理解 泛型)。
通配符在类型系统中的作用部分来自其不会发生协变(covariant)这一特性 。数组是协变的,因为 Integer 是 Number 的子类型,数组类型 Integer[] 是 Number[] 的子类型,因此在任何需要 Number[] 值的地方都可以提供一个 Integer[] 值。另一方面,泛型不是协变的, List<Integer> 不是 List<Number> 的子类型,试图在要求 List<Number> 的位置提供 List<Integer> 是一个类型错误。这不算很严重的问题 — 也不是所有人 都认为的错误 — 但泛型和数组的不同行为的确引起了许多混乱。
我已使用了一个通配符 — 接下来呢?
清单 1 展示了一个简单的容器(container)类型 Box,它支持 put 和 get 操作。 Box 由类型参数 T 参数化,该参数表示 Box 内容的类型, Box<String> 只能包含 String 类型的元素。
清单 1. 简单的泛型 Box 类型
public interface Box<T> {
public T get();
public void put(T element);
}
通配符的一个好处是允许编写可以操作泛型类型变量的代码,并且不需要了 解其具体类型。例如,假设有一个 Box<?> 类型的变量,比如清单 2 unbox() 方法中的 box 参数。unbox() 如何处理已传递的 box?
清单 2. 带有通配符参数的 Unbox 方法
public void unbox(Box<?> box) {
System.out.println(box.get());
}
事实证明 Unbox 方法能做许多工作:它能调用 get() 方法,并且能调用任 何从 Object 继承而来的方法(比如 hashCode())。它惟一不能做的事是调用 put() 方法,这是因为在不知道该 Box 实例的类型参数 T 的情况下它不能检验 这个操作的安全性。由于 box 是一个 Box<?> 而不是一个原始的 Box, 编译器知道存在一些 T 充当 box 的类型参数,但由于不知道 T 具体是什么, 您不能调用 put() 因为不能检验这么做不会违反 Box 的类型安全限制(实际上 ,您可以在一个特殊的情况下调用 put():当您传递 null 字母时。我们可能不 知道 T 类型代表什么,但我们知道 null 字母对任何引用类型而言是一个空值 )。
关于 box.get() 的返回类型,unbox() 了解哪些内容呢?它知道 box.get() 是某些未知 T 的 T,因此它可以推断出 get() 的返回类型是 T 的擦除 (erasure),对于一个无上限的通配符就是 Object。因此清单 2 中的表达式 box.get() 具有 Object 类型。