精美打印

通用类被所有调用共享

以下代码片段打印什么?

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

您可能会想说false,但是您错了。它打印true,因为通用类的所有实例都具有相同的运行时类,而不管其实际类型参数如何。

确实,使类具有泛型的原因是,它对所有可能的类型参数都具有相同的行为。可以将同一类视为具有许多不同的类型。

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

Cast 和 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;
}

类型变量在运行时不存在。这意味着它们在时间和空间上都没有性能开销,这很好。不幸的是,这也意味着您不能在演员表中可靠地使用它们。

Arrays

数组对象的组件类型可能不是类型变量或参数化类型,除非它是(无界)通配符类型。您可以声明其元素类型是类型变量或参数化类型的数组类型,但不能声明数组对象。

当然,这很烦人。为避免出现以下情况,必须使用此限制:

// 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];

同样,try创建元素类型为类型变量的数组对象会导致编译时错误:

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

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

解决此类限制的方法是将类 Literals 用作运行时类型标记,如下一节类 Literals 作为运行时类型令牌所述。