系列中的上一篇
当前教程
泛型限制
这是本系列的最后一篇!

系列中的上一篇: 类型擦除

泛型限制

 

无法使用基本类型实例化泛型类型

考虑以下参数化类型

class Pair<K, V> {

    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    // ...
}

在创建 Pair 对象时,不能将基本类型替换为类型参数 KV

Pair<int, char> p = new Pair<>(8, 'a');  // compile-time error

只能将非基本类型替换为类型参数 KV

Pair<Integer, Character> p = new Pair<>(8, 'a');

请注意,Java 编译器会自动装箱 8Integer.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<>();

由于静态字段 osphonepagerpc 共享,那么 os 的实际类型是什么?它不能同时是 SmartphonePagerTabletPC。因此,无法创建类型参数的静态字段。

 

无法对参数化类型使用强制转换或 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>。最多只能使用无界通配符来验证列表是否为 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>[2];  // 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) { }
}

所有重载将共享相同的类文件表示,并将生成编译时错误。


上次更新: 2021 年 9 月 14 日


系列中的上一篇
当前教程
泛型限制
这是本系列的最后一篇!

系列中的上一篇: 类型擦除