为了有效地使用Java泛型,必须考虑以下限制:
- 无法实例化具有原始类型的泛型类型
- 无法创建类型参数的实例
- 无法声明类型为类型参数的静态字段
- 无法将Casts或instanceof与参数化类型一起使用
- 无法创建参数化类型的数组
- 无法创建,捕获或抛出参数化类型的对象
- 无法重载每个重载的形式参数类型都擦除为相同原始类型的方法
无法实例化具有原始类型的泛型类型
考虑以下参数化类型:
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<>();
因为静态字段os由phone,pager和pc共享,所以os的实际类型是什么?它不能同时是Smartphone,Pager和TabletPC。因此,您无法创建类型参数的静态字段。
无法将Casts或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
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.
如果您对泛型列表尝试相同的操作,则会出现问题:
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
方法无法捕获类型参数的实例:
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) { }
}
重载将共享相同的类文件表示形式,并且将生成编译时错误。