文档

Java™教程
隐藏目录
泛型限制
指南:学习Java语言
课程:泛型(已更新)

泛型的限制

要有效地使用Java泛型,您必须考虑以下限制:

无法使用原始类型实例化泛型类型

考虑以下参数化类型:

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');  // 编译时错误

您只能为类型参数KV替换非原始类型:

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();  // 编译时错误
    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>) {  // 编译错误
        // ...
    }
}

传递给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要求一个可具体化的类型
        // ...
    }
}

通常情况下,除非参数化类型是由无界通配符参数化的,否则无法将其强制转换为参数化类型。例如:

List<Integer> li = new ArrayList<>();
List<Number>  ln = (List<Number>) li;  // 编译错误

但是,在某些情况下,编译器知道类型参数始终有效,并允许强制转换。例如:

List<String> l1 = ...;
ArrayList<String> l2 = (ArrayList<String>)l1;  // OK

无法创建参数化类型的数组

无法创建参数化类型的数组。例如,以下代码无法编译:

List<Integer>[] arrayOfLists = new List<Integer>[2];  // 编译错误

以下代码说明了在数组中插入不同类型时会发生什么:

Object[] strings = new String[2];
strings[0] = "hi";   // 正常
strings[1] = 100;    // 抛出 ArrayStoreException 异常。

如果使用泛型列表尝试同样的操作,会有问题:

Object[] stringLists = new List<String>[2];  // 编译错误,但假装它是被允许的
stringLists[0] = new ArrayList<String>();   // 正常
stringLists[1] = new ArrayList<Integer>();  // 应该抛出 ArrayStoreException 异常,
                                            // 但运行时无法检测到。

如果允许创建参数化列表的数组,前面的代码将无法抛出所需的 ArrayStoreException 异常。

无法创建、捕获或抛出参数化类型的对象

一个泛型类不能直接或间接地扩展 Throwable 类。例如,以下类将无法编译:

// 间接扩展 Throwable
class MathException<T> extends Exception { /* ... */ }    // 编译错误

// 直接扩展 Throwable
class QueueFullException<T> extends Throwable { /* ... */ // 编译错误

一个方法不能捕获类型参数的实例:

public static <T extends Exception, J> void execute(List<J> jobs) {
    try {
        for (J job : jobs)
            // ...
    } catch (T e) {   // 编译错误
        // ...
    }
}

然而,你可以在 throws 子句中使用类型参数:

class Parser<T extends Exception> {
    public void parse(File file) throws T {     // 正常
        // ...
    }
}

无法对形参类型擦除后相同原始类型的方法进行重载

一个类不能有两个重载方法,经过类型擦除后它们的签名相同。

public class Example {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

这些重载方法会共享相同的类文件表示,并产生编译错误。


上一页: 非可具体化类型
下一页: 问题和练习: 泛型