Java™ 教程(泛型的限制)

泛型的限制

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

  • 没法使用基元类型实例化泛型类型
  • 没法建立类型参数的实例
  • 没法声明类型为类型参数的静态字段
  • 没法对参数化类型使用强制类型转换或instanceof
  • 没法建立参数化类型的数组
  • 没法建立、捕获或抛出参数化类型的对象
  • 没法重载将每一个重载的形式参数类型擦除为相同原始类型的方法

没法使用基元类型实例化泛型类型

考虑如下参数化类型:数组

class Pair<K, V> {

    private K key;
    private V value;

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

    // ...
}

建立Pair对象时,不能将基本类型替换为类型参数KVapp

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

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

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

请注意,Java编译器将8自动装箱到Integer.valueOf(8),将'a'自动装箱到Character('a')this

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

有关自动装箱的详细信息,请参阅自动装箱和拆箱code

没法建立类型参数的实例

你没法建立类型参数的实例,例如,如下代码致使编译时错误:对象

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

做为解决方法,你能够经过反射建立类型参数的对象:get

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);

没法声明类型为类型参数的静态字段

类的静态字段是类的全部非静态对象共享的类级变量,所以,类型参数的静态字段是不容许的,考虑如下类:string

public class MobileDevice<T> {
    private static T os;

    // ...
}

若是容许类型参数的静态字段,则如下代码将混淆:

MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();

由于静态字段os是由phonepagerpc共享的,因此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>[];  // 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) { }
}

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


上一篇:类型擦除

下一篇:建立和使用包

相关文章
相关标签/搜索