泛型的限制
要有效地使用Java泛型,必须考虑以下限制:
- 无法使用基元类型实例化泛型类型
- 无法创建类型参数的实例
- 无法声明类型为类型参数的静态字段
- 无法对参数化类型使用强制类型转换或
instanceof
- 无法创建参数化类型的数组
- 无法创建、捕获或抛出参数化类型的对象
- 无法重载将每个重载的形式参数类型擦除为相同原始类型的方法
无法使用基元类型实例化泛型类型
考虑以下参数化类型:
class Pair{ private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } // ...}
创建Pair
对象时,不能将基本类型替换为类型参数K
或V
:
Pairp = new Pair<>(8, 'a'); // compile-time error
你只能将非基本类型替换为类型参数K
和V
:
Pairp = new Pair<>(8, 'a');
请注意,Java编译器将8
自动装箱到Integer.valueOf(8)
,将'a'
自动装箱到Character('a')
:
Pairp = new Pair<>(Integer.valueOf(8), new Character('a'));
有关自动装箱的详细信息,请参阅。
无法创建类型参数的实例
你无法创建类型参数的实例,例如,以下代码导致编译时错误:
public staticvoid append(List list) { E elem = new E(); // compile-time error list.add(elem);}
作为解决方法,你可以通过反射创建类型参数的对象:
public staticvoid append(List list, Class cls) throws Exception { E elem = cls.newInstance(); // OK list.add(elem);}
你可以按如下方式调用append
方法:
Listls = new ArrayList<>();append(ls, String.class);
无法声明类型为类型参数的静态字段
类的静态字段是类的所有非静态对象共享的类级变量,因此,类型参数的静态字段是不允许的,考虑以下类:
public class MobileDevice{ private static T os; // ...}
如果允许类型参数的静态字段,则以下代码将混淆:
MobileDevicephone = new MobileDevice<>();MobileDevice pager = new MobileDevice<>();MobileDevice pc = new MobileDevice<>();
因为静态字段os
是由phone
、pager
和pc
共享的,所以os
的实际类型是什么?它不能同时是Smartphone
、Pager
和TabletPC
,因此,你无法创建类型参数的静态字段。
无法对参数化类型使用强制类型转换或instanceof
因为Java编译器会擦除泛型代码中的所有类型参数,所以无法验证在运行时使用泛型类型的参数化类型:
public staticvoid rtti(List list) { if (list instanceof ArrayList ) { // compile-time error // ... }}
传递给rtti
方法的参数化类型集是:
S = { ArrayList, ArrayList LinkedList , ... }
运行时不跟踪类型参数,因此它无法区分ArrayList<Integer>
和ArrayList<String>
之间的区别,你可以做的最多是使用无界通配符来验证列表是否为ArrayList
:
public static void rtti(List list) { if (list instanceof ArrayList ) { // OK; instanceof requires a reifiable type // ... }}
通常,除非通过无界通配符对其进行参数化,否则无法强制转换为参数化类型,例如:
Listli = new ArrayList<>();List ln = (List ) li; // compile-time error
但是,在某些情况下,编译器知道类型参数始终有效并允许强制转换,例如:
Listl1 = ...;ArrayList l2 = (ArrayList )l1; // OK
无法创建参数化类型的数组
你无法创建参数化类型的数组,例如,以下代码无法编译:
List[] arrayOfLists = new List [2]; // compile-time error
以下代码说明了将不同类型插入到数组中时会发生什么:
Object[] strings = new String[2];strings[0] = "hi"; // OKstrings[1] = 100; // An ArrayStoreException is thrown.
如果你使用泛型列表尝试相同的操作,则会出现问题:
Object[] stringLists = new List[]; // compiler error, but pretend it's allowedstringLists[0] = new ArrayList (); // OKstringLists[1] = new ArrayList (); // An ArrayStoreException should be thrown, // but the runtime can't detect it.
如果允许参数化列表数组,则前面的代码将无法抛出所需的ArrayStoreException
。
无法创建、捕获或抛出参数化类型的对象
泛型类不能直接或间接扩展Throwable
类,例如,以下类将无法编译:
// Extends Throwable indirectlyclass MathExceptionextends Exception { /* ... */ } // compile-time error// Extends Throwable directlyclass QueueFullException extends Throwable { /* ... */ // compile-time error
方法无法捕获类型参数的实例:
public staticvoid execute(List jobs) { try { for (J job : jobs) // ... } catch (T e) { // compile-time error // ... }}
但是,你可以在throws
子句中使用类型参数:
class Parser{ public void parse(File file) throws T { // OK // ... }}
无法重载将每个重载的形式参数类型擦除为相同原始类型的方法
一个类不能有两个在类型擦除后具有相同的签名的重载方法。
public class Example { public void print(SetstrSet) { } public void print(Set intSet) { }}
重载将共享相同的类文件表示,并将生成编译时错误。