泛型--泛型的限制

不可具体化类型

一个具体化类型是其信息在运行时是完全可知的。这包括原始类型,非泛型类型,原类型,和非受限通配符范型调用。

 

不可具体化类型是其信息在编译时的类型擦除是被去掉了——没有定义成非受限的通配符范型调用。一个不可具体化类型在运行时其信息不完全可知。List<String>和List<Number>是不可具体化类型;JVM在运行时无法知道它们间的区别。如在“泛型的限制”一节中,有些情况下是不能使用不可具体化类型:在 instanceof 表达式中,在数组元素中。

 

堆污染

当参数化类型变量引用一个非参数化类型对象时,就会发生堆污染现象。这种情况发生在当程序执行一些会产生编译时的非受检警告(unchecked warning)时。

 

 

 

为了更有效的使用Java的泛型,你必须考虑到下面的这些限制:

n  不能使用原始类型来实例化泛型类型

n  不能创建类型参数的实例

n  不能声明类型为类型参数的静态字段

n  不能在参数化类型上使用映射(cast)和instanceof

n  不能创建参数化类型数组

n  不能创建,捕获,抛出参数化类型对象

n  不能重载形式类型参数擦除后得到相同的原类型的方法

 

不能使用原始类型来实例化泛型类型

考虑下面参数化类型:

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

 

因为phone,pager,和pc共享静态字段os,那么os该是什么类型呢?它不能同时是Smartphone,Pager,和TabletPC。因此,你不能创建类型参数的静态字段。

 

不能在参数化类型上使用映射(cast)和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>间的不同。你最多能做的是使用上界通配符来验证list是一个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.

 

 

如果你对泛型list做同样的事,将会产生问题:

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

A method cannot catch an instance

 

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

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) { }
}

 

这两个重载共享同样的类文件(classfile)的表示,将产生编译错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值