以下代码片段打印什么?
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;
给出一个未经检查的警告,因为这不是运行时系统要检查的东西。
类型变量也是如此
// 未经检查的警告
<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
为数组。
// OK,无界通配符类型数组。
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]; // 错误
}
由于类型变量在运行时不存在,因此无法确定实际的数组类型。
解决这些种类的限制的方法是使用类文字作为运行时类型的标记,比如在下一节中描述的那样, 类常量作为运行时类型令牌