A.2 众所周知的事实
在使用 List 或 Set 这样的集合时,可以将元素的类名置于尖括号中,以声明所包含元素的类型:
List<String> strings = new ArrayList<String>();Set<Employee> employees = new HashSet<Employee>();
通过在之后的示例代码中引入钻石运算符(diamond operator),Java 7 能在一定程度上简化语法。由于等号左侧的引用已经声明了集合以及所包含的类型(如
List或List),等号右侧的实例化无须再次声明。我们可以将其简写为new ArrayList<>(),而不必将类型置于尖括号中。
声明集合的数据类型可以实现两个目的:
- 能避免不慎将错误的类型置于集合中
- 无须再将检索到的值强制转换为合适的类型
如例 A-1 所示,在声明 strings 变量之后,就只能向集合添加 String 实例,并在检索到某项时自动获得一个 String。
例 A-1 简单的泛型示例
- List<String> strings = new ArrayList<>();
- strings.add("Hello");
- strings.add("World");
- // strings.add(new Date()); ➊
- // Integer i = strings.get(0); ➊
- for (String s : strings) { ➋
- System.out.printf("%s has length %d%n", s, s.length());
- }
❶ 无法编译
❷ for-each 循环了解所包含的数据类型为 String
对插入过程应用类型安全(type safety)很方便,但开发人员很少会犯这个错误。不过,如果不必首先强制转换就可以处理检索类型,能极大简化代码。1
1在整个职业生涯中,我从未不慎将错误的类型添加到列表中。不过即便只是考虑到糟糕的语法,去掉强制转换过程也是值得的。
另一个众所周知的事实是,无法为泛型集合添加基本数据类型(primitive type)。换言之,目前尚无法定义 List 或 List。2 幸运的是,Java 1.5 在引入泛型的同时也引入了自动装箱和拆箱。因此,如果希望在泛型类型(generic type)中储存基本数据类型,可以通过包装类(wrapper class)声明该类型,如例 A-2 所示。
2Java 10(Valhalla 项目)已提出将基本数据类型添加到集合中。
例 A-2 在泛型集合中使用基本数据类型
- List<Integer> ints = new ArrayList<>();
- ints.add(3); ints.add(1); ints.add(4);
- ints.add(1); ints.add(9); ints.add(2);
- System.out.println(ints);
- for (int i : ints) {
- System.out.println(i);
- }
可以看到,Java 在插入时将 int 值包装在 Integer 实例中,并在检索时从 Integer 实例中取出这些值。尽管装箱和拆箱的效率有待商榷,但代码确实很容易编写。
此外,Java 开发人员耳熟能详的一点是,如果一个类使用了泛型,那么类型本身采用尖括号中的大写字母表示。例如,Javadoc 对 java.util.List 接口的描述如下:
public interface List<E> extends Collection<E>
其中 E 是类型参数(type parameter),且接口中的方法使用相同的类型参数。例 A-3 显示了 List 接口声明的部分方法。
例 A-3
List接口声明的部分方法
boolean add(E e) ➊boolean addAll(Collection<? extends E> c) ➋void clear() ➌boolean contains(Object o) ➌boolean containsAll(Collection<?> c) ➍E get(int index) ➊
❶ 类型参数 E 用作参数或返回类型
❷ 有界通配符
❸ 与类型本身无关的方法
❹ 未知类型
可以看到,某些方法使用声明的泛型类型 E 作为参数或返回类型,某些方法(特别是 clear 和 contains)完全不使用类型,还有部分方法使用问号作为通配符。
请注意,在非泛型类中声明泛型方法是合法的。这种情况下,泛型参数被声明为方法签名的一部分。以工具类 java.util.Collections 为例,它定义了以下静态方法:
- static <T> List<T> emptyList()
- static <K,V> Map<K,V> emptyMap()
- static <T> boolean addAll(Collection<? super T> c, T... elements)
- static <T extends Object & Comparable<? super T>>
- T min(Collection<? extends T> coll)
如上所示,emptyList、addAll、min 这三种方法声明了泛型参数 T。emptyList 方法通过 T 来指定 List 中包含的类型,而 emptyMap 方法在泛型映射中使用 K 和 V 来表示键的类与值的类。
addAll 方法声明了泛型类型 T,并使用 Collection c 作为方法的第一个参数,T 类型的可变参数列表作为第二个参数。? super T 是一种有界通配符(bounded wildcard),稍后将对此做讨论。
从 min 方法可以看出泛型类型是如何提供安全性的,但其签名结构或许不那么一目了然。后面将详细讨论该方法的签名,目前不妨这样理解:T 是有界的,它既是 Object 的子类,又实现了 Comparable 接口,其中 Comparable 定义为 T 或 T 的任何父类。min 方法的参数与 T 或 T 的任何子类的 Collection 有关。
最后,通配符将众所周知的语法以我们不那么熟悉的形式表现出来。例如,某些语法看似继承,但实际上根本不是。
通过在之后的示例代码中引入钻石运算符(diamond operator),Java 7 能在一定程度上简化语法。由于等号左侧的引用已经声明了集合以及所包含的类型(如 