泛型类由其所有调用共享
以下代码片段打印什么?
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;
}
类型变量在运行时不存在。这意味着它们在时间和空间上都没有性能开销,这很好。不幸的是,这也意味着您不能可靠地在转换中使用它们。
数组
数组对象的组件类型可能不是类型变量或参数化类型,除非它是(无界)通配符类型。您可以声明其元素类型是类型变量或参数化类型的数组类型,但不能声明数组objects。
当然,这很烦人。为避免出现以下情况,必须使用此限制:
// 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.
}
由于类型变量在运行时不存在,因此无法确定实际的数组类型。
要解决这些类型的限制的方法是使用类字面量作为运行时类型的标记,比如在下一节中,描述 类字面量作为运行时类型标记。