文档

Java™ 教程-Java Tutorials 中文版
附属细则
Trail: Bonus
Lesson: Generics

附属细则

泛型类由其所有调用共享

以下代码片段会打印什么?

List <String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());

你可能想说 false,但你错了。它打印 true,因为泛型类的所有实例都具有相同的运行时类,不论它们的实际类型形参(这里应该是实参)是什么。

实际上,使类具有泛型性的原因在于它对所有可能的类型形参具有相同的行为;可以将同一类视为具有许多不同类型。

因此,类的静态变量和静态方法也在所有实例之间共享。这就是为什么在静态方法或初始化程序中,或在静态变量的声明或初始化程序中引用类型声明的类型形参是非法的。

强制转换和 InstanceOf

泛型类在其所有实例之间共享这一事实的另一个含义是,询问实例是否是泛型类型的特定调用的实例通常是没有意义的:

Collection cs = new ArrayList<String>();
// Illegal.
if (cs instanceof Collection<String>) { ... }

同样,强制转换比如

// Unchecked warning,
Collection<String> cstr = (Collection<String>) cs;

给出一个未经检查的警告,因为这不是运行时系统要检查的东西。

类型变量也是如此

// Unchecked warning. 
<T> T badCast(T t, Object o) {
    return (T) o;
}

运行时不存在类型变量。这意味着它们在时间和空间上都不会产生性能开销,这很好。不幸的是,这也意味着你无法在强制转换中可靠地使用它们。

数组

数组对象的组件类型不能是类型变量或参数化类型,除非它是(无界)通配符类型。你可以声明数组 类型 其元素类型为类型变量或参数化类型,但不能创建数组 对象 其元素类型为类型变量或参数化类型。(译注:这里的用语主要区分对象和类型)

这很烦人,可以肯定。这种限制是必要的,以避免以下情况:

// Not really allowed.
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));
// Unsound, but passes run time store check
oa[1] = li;

// Run-time error: ClassCastException.
String s = lsa[1].get(0);

如果允许参数化类型的数组,则前一个示例将在没有任何未经检查的警告的情况下进行编译,但在运行时仍然失败。我们将类型安全作为泛型的主要设计目标。特别是,则该语言旨在保证 如果使用 javac -source 1.5 编译整个应用程序而没有未经检查的警告,则它是类型安全的

但是,你仍然可以使用通配符数组。前面代码的以下变体放弃了使用数组对象和元素类型是参数化的数组类型。结果,我们必须显式地转换以从数组中获取 String

// OK, array of unbounded wildcard type.
List<?>[] lsa = new List<?>[10];
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
// Correct.
oa[1] = li;
// Run time error, but cast is explicit.
String s = (String) lsa[1].get(0);

在下一个变体中会导致编译时错误,我们避免创建其元素类型是参数化的数组对象,但仍使用具有参数化元素类型的数组类型。

// Error.
List<String>[] lsa = new List<?>[10];

类似地,尝试创建元素类型为类型变量的数组对象会导致编译时错误:

<T> T[] makeArray(T t) {
    return new T[100]; // Error.
}

由于类型变量在运行时不存在,因此无法确定实际的数组类型。

解决这些限制的方法是使用类字面量作为运行时类型标记,如下一节 Class Literals as Runtime-Type Tokens 中所述。


Previous page: Interoperating with Legacy Code
Next page: Class Literals as Runtime-Type Tokens