泛型-通配符的使用

来源:互联网 发布:python list 拆分 编辑:程序博客网 时间:2024/06/01 09:42

泛型是一种表示类型约束的手段,比如某一泛型类Generic<T>,它表示的意思是:虽然我不知道T是什么类型,但是在Generic<T>中,所有的T必须是相同的类型。通配符就是使用问号来表示未知的类型,比如List<?>。通配符确实相当复杂,为了理解通配符必须抓住两点:Java的泛型是使用擦除来实现的,运行时不知道确切的类型;泛型的主要目的是增强类型的安全性,尤其是类型安全性对于理解通配符相当重要。

List<?>,List和List<Object>是不同的,List是异构的,你可以往里面添加任何东西;List<Object>表示我们明确知道它包含Object对象;而List<?>表示:某种特定类型的List,虽然我不知道是哪种类型。

泛型不是协变的

数组是协变的,Integer是Number的子类,Integer[]也是Number[]的子类。但是泛型不是协变的,String是Object的子类,但是List<String>不是List<Object>的子类,List<String>可以持有String及String的子类类型,List<Object>可以持有Object及Object的子类,包括String。真正的问题是容器的类型,而不是容器持有的类型,List<String>是一种类型,List<Object>是一种类型,下面的语句是无法通过编译的:

List<Object> list = new List<String>();

无界通配符

<?>是一个无界通配符,它表示我想用Java的泛型来写这段代码,我在这里并不是要用原生类型,但在当前这种情况下,泛型参数可以持有任何类型。下面是使用无界通配符的一个例子:

        //一个泛型类class List<T> {public T get() {}public void set(T t) {}}public void f(List<?> list) {Object obj = list.get();//oklist.set(obj);//编译不通过}
方法f接受一个List<?>,编译器这个时候虽然不知道List持有的是什么类型,但是知道存在这样的类型T,所以get()方法返回的就是T的擦除,对于无界通配符的擦除就是Object,所以get()返回的是Object。重点是set()方法,刚刚从get()返回的对象,竟然无法放到set里,这是不是很奇怪?为了理解这个问题,必须得从安全性思考,编译器知道set能接受某种类型T,但是T是什么类型呢,它不知道,所以它就无法验证参数的安全性,所以它会拒绝编译。List<?>是同构的,虽然不知道它包含的是什么类型的元素,但它的所有元素必然都是同一类型的,编译器要保证同构,就不得不拒绝所有它无法验证的类型。

上面的编译错误是:The method set(capture#7-of ?) in the type List<capture#7-of ?> is not applicable for the arguments (Object).capture#7-of ?表示什么?当编译器遇到带有通配符的变量,比如List<?>,它认识到必然存在某种类型的T,使得list是List<T>类型,但是它不知道T是什么类型,所以它使用占位符来代替T,占位符称为特殊通配符的捕获(capture),(具体的内容可参考Goetz的文章:http://www.ibm.com/developerworks/cn/java/j-jtp04298.html)。错误的提示是不能调用set()方法,因为不能验证set()方法的实参与形参是否兼容-因为形参的类型是未知的。set()的形参此时由capture#7-of ?表示,而我们传递的实参(经过编译器的推断,知道get()方法返回的是Object)是Object,编译器无法判断对于capature#7-of ?而言,Object是否是一个可接受的类型,所以它拒绝。为了绕开编译器的限制,可以使用捕获助手:

private <T> void capatureHelper(List<T> list) {    list.set(list.get());}

借助于泛型方法,现在编译器知道list是List<T>类型的,get()返回的是T类型,set()接受的参数也是T类型。

上界通配符

为了对类型信息进行验证,Java重用了extends关键字,? extends Number表示的是Number或者Number的子类,这样就把类型信息限制在Number这个级别了,Number就是该泛型能表示的上界(想象一下类的结构图,父类画在上面),也就是说该泛型至少是Number类型的。

public void f(List<? extends Number> list)  {   Number num = list.get(0);   Object obj = list.get(1);    //complie error    list.set(1);}
编译器可以推断出,list持有的至少是Number,所以get()返回的也至少是Number。但是set()方法却无法通过编译,这是为什么?list是同构的,且至少持有的是Number,那么list可以是List<Integer>,也可以是List<Double>,也可以是List<Float>,编译器能知道它具体是哪种类型吗?不能,既然不知道具体的类型,怎么往里面添加数据呢!比如上面的代码,往里面添加了Integer,但是如果传递进来的是List<Double>,岂不是出错了!所以为了安全,编译器拒绝一切值插进去(null除外,因为对null对任何类型而言都是合法的)。

下界通配符

Java又重用了super关键自,? super Number表示的是Number或者Number的父类,这样Number就是下界了,比如List<? super Number>表示它的类型是Number的父类,所以它可以持有Number和Number的子类,Number是下界。

public void f(List<? super Number> list)  {    //compile error   Number num = list.get(0);   list.add(1);//ok   list.add(1.1);//ok}
在上面的代码中,编译器能获知的信息是List<T>,T是Number或者Number的父类,但是到底是什么,它不知道,List<Number>是合法的,List<Object>也是合法的,所以通过list.get()获得类型信息就被擦除到了Object类型。但是编译器知道它至少可以持有Number和它的子类,所以往里面添加Number的子类是安全的。但是往里面添加Object类型的数据则是不安全的,因为有可能list是List<Number>。

转载请注明:喻红叶《泛型-通配符的使用》

0 0