Java教程是为JDK 8编写的。本页中描述的示例和实践不利用后续版本引入的改进,并可能使用不再可用的技术。
有关Java SE 9及后续版本中更新的语言特性的摘要,请参阅Java语言更改。
有关所有JDK版本的新功能、增强功能和已删除或弃用选项的信息,请参阅JDK发行说明。
以下代码片段会打印出什么?
List<String> l1 = new ArrayList<String>(); List<Integer> l2 = new ArrayList<Integer>(); System.out.println(l1.getClass() == l2.getClass());
你可能会觉得会打印false
,但实际上是错误的。它会打印true
,因为所有泛型类的实例在运行时都具有相同的类,而不管它们的实际类型参数是什么。
事实上,使类成为泛型的原因是它对所有可能的类型参数具有相同的行为;同一个类可以被看作具有许多不同的类型。
因此,类的静态变量和方法也被所有实例共享。这就是为什么在静态方法或初始化程序中引用类型声明的类型参数,或者在静态变量的声明或初始化程序中引用类型参数是非法的。
泛型类被所有实例共享的另一个影响是,通常没有意义询问一个实例是否是特定调用的泛型类型的实例:
Collection cs = new ArrayList<String>(); // 非法。 if (cs instanceof Collection<String>) { ... }
类似地,下面的强制类型转换
// 未经检查的警告, Collection<String> cstr = (Collection<String>) cs;
会产生一个未经检查的警告,因为这不是运行时系统将为你检查的内容。
对于类型变量也是如此
// 未经检查的警告. <T> T badCast(T t, Object o) { return (T) o; }
类型变量在运行时不存在。这意味着它们在时间和空间上都不会产生性能开销,这很好。不幸的是,这也意味着您不能可靠地在强制类型转换中使用它们。
数组对象的组件类型可能不是类型变量或参数化类型,除非它是(无界)通配符类型。您可以声明数组的类型,其元素类型是类型变量或参数化类型,但不能声明数组的对象。
这确实很让人讨厌。这个限制是为了避免出现以下情况:
// 实际上是不允许的。 List<String>[] lsa = new List<String>[10]; Object o = lsa; Object[] oa = (Object[]) o; List<Integer> li = new ArrayList<Integer>(); li.add(new Integer(3)); // 不安全,但通过运行时存储检查 oa[1] = li; // 运行时错误:ClassCastException。 String s = lsa[1].get(0);
如果允许使用参数化类型的数组,前面的例子将编译通过且没有任何未经检查的警告,但在运行时会失败。我们将类型安全作为泛型的主要设计目标。特别是,该语言被设计为保证如果您的整个应用程序在使用javac -source 1.5编译时没有未经检查的警告,那么它是类型安全的。
然而,您仍然可以使用通配符数组。下面是前面代码的变体,放弃了使用数组对象和元素类型为参数化的数组类型。因此,我们必须进行显式转换才能从数组中获取一个String
。
// 好的,无界通配符类型的数组。 List<?>[] lsa = new List<?>[10]; Object o = lsa; Object[] oa = (Object[]) o; List<Integer> li = new ArrayList<Integer>(); li.add(new Integer(3)); // 正确。 oa[1] = li; // 运行时错误,但是转换是显式的。 String s = (String) lsa[1].get(0);
在下一个变体中,会导致编译时错误,我们避免创建元素类型为参数化的数组对象,但仍然使用具有参数化元素类型的数组类型。
// 错误。 List<String>[] lsa = new List<?>[10];
类似地,尝试创建元素类型为类型变量的数组对象会导致编译时错误:
<T> T[] makeArray(T t) { return new T[100]; // 错误。 }
由于类型变量在运行时不存在,因此无法确定实际的数组类型。
解决这些限制的方法是使用类字面值作为运行时类型标记,如下一节运行时类型标记的类字面值中所述。