java泛型常见问题

来源:互联网 发布:淘宝卖家后台登录 编辑:程序博客网 时间:2024/05/16 18:32

在《Java泛型总结》中已经详细介绍了泛型的相关知识,本文继续来看几个问题。

java泛型的通过对变量进行类型限制,使得在编译阶段尽可能发现更多的错误,因此强化了类型安全,同时消除了许多强制类型转换。为了与JDK5.0之前的版本保持兼容,编译器会把泛型代码首先转换为不带泛型的代码,具体分为以下几步:

1.将参数化类型中的类型参数"擦除"(erasure)掉;

2.将类型变量用"上限(upper bound)"取代,通常情况下这些上限是 Object。这里的类型变量是指实例域,本地方法域,方法参数以及方法返回值中用来标记类型信息的"变量",例如:实例域中的变量声明 A elem;方法声明 Node (A elem){};,其中,A 用来标记 elem 的类型,它就是类型变量。

3.添加类型转换并插入"桥方法"(bridge method),以便覆盖(overridden)可以正常的工作。

下面结合一些具体的问题来分析。

public class Collections {    public static <T> void copy(List<? super T> dest, List<? extends T> src) {        for (int i=0; i<src.size();i++)           dest.set(i,src.get(i));     }   }

Java Collections中的拷贝函数如上所示,首先这里T作为类型变量,其中两个比较重要的用法是? extends T和? super T。他们之间是有非常大的区别的。? extends T说明某个类型继承自T,但是具体是什么类型不清楚,就好比化学实验室中有很多器皿,对于某个器皿我们只知道它是用来盛液体的,但是至于是那种液体无从而知,我们能把硫酸倒入进去吗?不能!说不定就会造成试剂污染。同样比如List<? extends Number>我们只知道list中存放的是Number类型的对象,但是却无法向里面添加元素,只能从中取数。 另一个方面,我们知道集合不同于数组,List<Number>不是List<Integer>的父类,所以不能进行赋值操作,? extends T的出现弥补了这一点。比如

List<Integer> listI = new ArrayList<Integer>();

List<Number> list = listI;

如果说? extends T指定了元素的上界,? super T 则指出了元素的下界。与? extends T不同的是它只能存数据不能取数据(这样说不是特别严格),因为我们只知道它是T的父类,但是同样无法确切的知道它的类型。(? extends T与? super T就好像各自指定了一个区间[null,T]---[T,Object])。

对于泛型有一个存取原则:如果只取数不放数,就用? extends T;如果只放数不取数,则用? super T;如果即取数又放数则用类型参数T。

像是上面代码中的copy函数就是对这个原则最好的诠释。关于copy函数,或许会有这样的疑问,这里可不可以把参数List<? super T>用List<T>代替,因为数据源一定继承自T嘛。看起来是这样的。但是有一点可以肯定,上面代码中的copy函数使用范围一定更广。(我还没有想到反例)

再看一段代码:

private Map<Integer, List<Integer>> a = new HashMap<Integer, ArrayList<Integer>>();
这段代码编译时会报错:不兼容的类型。上面说过泛型的主要目的是对元素类型进行限制,加强类型安全,减少强制转换。这里我们看一下这段代码,为什么会报错呢?假如编译器允许这段代码通过检查,那么,map中第二个元素就不仅可以存放ArrayList<Integer>还可以存放其他实现了List<Integer>但不是ArrayList<Integer>的对象,这不符合我们的预期。类似的,List<Number> list = new ArrayList<Integer>();会产生编译错误,不要与下面这种情况混淆:

List<Number> list= new ArrayList<Number>();
list.add(new Integer(1));

接着看另外一段代码:

public static <E extends Number> List<E> group(E ... numbers) {    return Arrays.asList(numbers);}
这样使用List<Integer> ints = group(1, 2, 3);没有问题,但是List<Number> ints = group(1, 2, 3);这样就出现问题了。这里面涉及到编译器对类型的推断,第二种情况中,编译器会根据参数类型推断E为整型,把List<Integer>引用赋值给List<Number>是错误的,二者没有继承关系。这时,我们需要显示的告诉编译器类型信息,可以这样使用List<Number> ints = MyClass.<Number>group(1, 2, 3);

最后再看一个来自statckoverflow的提问:

import java.util.*;public class Test {    public <T> void createA(List<I<T>> c) {        A a = new A(c);//warning here    }    public <T> void createB(List<I<T>> c) {        B b = new B(c);//error here: The constructor B(List<I<T>>) is undefined    }}interface I<T>{}
class B implements I<Integer>{    public B(List<I<?>> c){    }}class A<T> implements I<T>{    public A(List<I<?>> c){    }}

qs:B class is generic and A is not, but I have no idea why it matters in that case.

ans:直接贴出回答

public B(List<I<?>> c) {
}
The ? is the unknown type. It is not a wild card for any other type. The compiler tells the truth, there is no constructor for the type List<I<T>> (T is not ?)

It doesn't really work for the other method too. You simply exchanged the compile error by a "unchecked conversion" warning. Because class A is parametized, you'd have to call it like that:

public <T> void createA(List<I<T>> c) {
    A<T> a = new A<T>(c);
}
And voilà, say hello to the same error.







0 0
原创粉丝点击