不可具体化类型
一个具体化类型是其信息在运行时是完全可知的。这包括原始类型,非泛型类型,原类型,和非受限通配符范型调用。
不可具体化类型是其信息在编译时的类型擦除是被去掉了——没有定义成非受限的通配符范型调用。一个不可具体化类型在运行时其信息不完全可知。List<String>和List<Number>是不可具体化类型;JVM在运行时无法知道它们间的区别。如在“泛型的限制”一节中,有些情况下是不能使用不可具体化类型:在 instanceof 表达式中,在数组元素中。
堆污染
当参数化类型变量引用一个非参数化类型对象时,就会发生堆污染现象。这种情况发生在当程序执行一些会产生编译时的非受检警告(unchecked warning)时。
为了更有效的使用Java的泛型,你必须考虑到下面的这些限制:
n 不能使用原始类型来实例化泛型类型
n 不能创建类型参数的实例
n 不能声明类型为类型参数的静态字段
n 不能在参数化类型上使用映射(cast)和instanceof
n 不能创建参数化类型数组
n 不能创建,捕获,抛出参数化类型对象
n 不能重载形式类型参数擦除后得到相同的原类型的方法
不能使用原始类型来实例化泛型类型
考虑下面参数化类型:
class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } // ... }
|
当创建一个Pair对象时,你不能用原始类型替换类型参数K,V:
Pair<int, char> p = new Pair<>(8, 'a'); // compile-time error
|
你只可以用非原始类型替换类型参数K和V:
Pair<Integer, Character> p = new Pair<>(8, 'a');
|
注意,Java编译器会自动装箱,8会变成Integer.valueOf(8),‘a’会变成Character(‘a’):
Pair<Integer, Character> p = new Pair<>(Integer.valueOf(8), new Character('a'));
|
不能创建类型参数的实例
你不能创建类型参数的实例。例如,下面的代码会产生一个编译错误:
public static <E> void append(List<E> list) { E elem = new E(); // compile-time error list.add(elem); }
|
变通方法是,你可以通过反射机制类型参数对象:
public static <E> void append(List<E> list, Class<E> cls) throws Exception { E elem = cls.newInstance(); // OK list.add(elem); } |
你可以用以下方式来调用append方法:
List<String> ls = new ArrayList<>(); append(ls, String.class);
|
不能声明类型为类型参数的静态字段
一个类的静态字段,可以被类的所有非静态对象共享的类的变量。因此,类型参数的静态字段是不允许的。考虑下面的代码:
public class MobileDevice<T> { private static T os; // ... }
|
如果允许静态类型参数字段,下面的代码将产生混淆:
MobileDevice<Smartphone> phone = new MobileDevice<>(); MobileDevice<Pager> pager = new MobileDevice<>(); MobileDevice<TabletPC> pc = new MobileDevice<>();
|
因为phone,pager,和pc共享静态字段os,那么os该是什么类型呢?它不能同时是Smartphone,Pager,和TabletPC。因此,你不能创建类型参数的静态字段。
不能在参数化类型上使用映射(cast)和instanceof
因为Java编译器会擦除所有泛型中的类型参数,在运行时,你不能区分正在使用的是泛型那个参数化类型:
public static <E> void rtti(List<E> list) { if (list instanceof ArrayList<Integer>) { // compile-time error // ... } }
|
传递给rtti方法的参数化类型的集合是:
S = { ArrayList<Integer>, ArrayList<String> LinkedList<Character>, ... }
|
运行时不会跟踪类型参数,因此不能区分ArrayList<Integer>和ArrayList<String>间的不同。你最多能做的是使用上界通配符来验证list是一个ArrayList:
public static void rtti(List<?> list) { if (list instanceof ArrayList<?>) { // OK; instanceof requires a reifiable type // ... } }
|
一般情形下,你不能映射一个参数化类型,除非是不受限的参数化。例如:
List<Integer> li = new ArrayList<>(); List<Number> ln = (List<Number>) li; // compile-time error
|
然而,在一些情况下,编译器知道一个类型参数总是有效的,并运行映射。例如:
List<String> l1 = ...; ArrayList<String> l2 = (ArrayList<String>)l1; // OK
|
不能创建参数化类型数组
你不能创建参数化类型数组。例如,下面的代码不能编译:
List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error
|
下面的代码演示了如果将不同类型插入进数组会发生什么:
Object[] strings = new String[2]; strings[0] = "hi"; // OK strings[1] = 100; // An ArrayStoreException is thrown.
|
如果你对泛型list做同样的事,将会产生问题:
Object[] stringLists = new List<String>[]; // compiler error, but pretend it's allowed stringLists[0] = new ArrayList<String>(); // OK stringLists[1] = new ArrayList<Integer>(); // An ArrayStoreException should be thrown, // but the runtime can't detect it.
|
如果允许参数化列表数组,上面的代码将不能抛出希望的ArrayStoreException。
不能创建,捕获,抛出参数化类型对象
一个泛型类不能从Throwable类直接或间接扩展。例如,下面的类不会通过编译:
// Extends Throwable indirectly class MathException<T> extends Exception { /* ... */ } // compile-time error // Extends Throwable directly class QueueFullException<T> extends Throwable { /* ... */ // compile-time error A method cannot catch an instance |
一个方法不能捕获类型参数的实例:
public static <T extends Exception, J> void execute(List<J> jobs) { try { for (J job : jobs) // ... } catch (T e) { // compile-time error // ... } }
|
你可以在throws子句中使用类型参数:
class Parser<T extends Exception> { public void parse(File file) throws T { // OK // ... } }
|
不能重载 形式类型参数擦除后得到相同的原类型的方法
一个类不能有两个重载的方法,如果它们在类型擦除后得到的方法签名是一样的。
public class Example { public void print(Set<String> strSet) { } public void print(Set<Integer> intSet) { } }
|
这两个重载共享同样的类文件(classfile)的表示,将产生编译错误。