Java泛型支持通配符(Wildcard),可以单独使用一个"?"表示任意类,也可以使用extends关键字标识某一类(接口)的子类型,还可以使用super关键字标识某一类(接口)的父类型,但问题是什么时候该用extends,什么时候该用super?
(1)泛型结构只参与"读"操作则限定上界(extends 关键字)
于都如下代码,看业务逻辑操作是否还能继续?
1 import java.util.Arrays; 2 import java.util.List; 3 4 public class Client { 5 public static void main(String[] args) { 6 read(Arrays.asList("A")); 7 8 } 9 10 public static <E> void read(List<? super E> list){ 11 12 for(Object obj:list){ 13 14 //业务逻辑操作 15 } 16 } 17 }
从List列表中读取元素操作(比如一个数字列表中的求和计算),你觉得方法read能继续写下去吗?
答案是不能,我们不知道list到底存放的是什么元素,只能推断出是E类型的父类,当然也可以是E类型,单问题是E类型的父类是什么呢?
无法再推断,只有运行时才知道,那么编码期就完全无法操作了,当然,你可以把它当作是Object类型来处理,需要时再转换成E类型,但是这样完全违背了泛型的初衷.
在这种情况下,"读"操作如果期望从List集合中读取数据就需要使用extends关键字了,也就是要界定泛型的上界,代码如下:
1 import java.util.Arrays; 2 import java.util.List; 3 4 public class Client { 5 public static void main(String[] args) { 6 read(Arrays.asList("A")); 7 } 8 9 public static <E> void read(List<? extends E> list){ 10 for(E e:list){ 11 System.out.println(e.getClass()); 12 //业务逻辑处理 13 } 14 } 15 }
此时已经推断出List集合中取出的是E类型的元素,具体是什么类型的元素就要等到运行时才能确定了,但是它一定是一个确定的类型,比如read(Arrays.asList("A"))调用该方法时,可以推断出List中的元素类型是String,之后就可以对List中的元素进行操作了,如加入到另外的List<E>集合中,或者作为Map<E,V>的键等.
(2)泛型结构只参与"写"操作则限定下界(使用super关键字)
先看如下代码是否可以正常编译:
1 import java.util.List; 2 3 public class Client { 4 public static void main(String[] args) { 5 6 } 7 8 public static void write(List<? extends Number> list){ 9 list.add(null); 10 list.add(123); 11 //The method add(int, capture#2-of ? extends Number) 12 //in the type List<capture#2-of ? extends Number> is not applicable for the arguments (int) 13 } 14 }
编译失败,失败的原因是list中的元素类型不确定,也就是编译期无法推断出泛型类型到底是什么,是Integer类型?是Double?还是Byte?这些都符合extends关键字的定义,由于无法确定实际的泛型类型,所以编译器聚聚了此类操作.
在此种情况下只有一个元素是可以add进去的:null值,这是因为null是一个万用类型,它可以是所有类的实例对象,所以可以加入到任何列表中.
Object是否也可以?不可以,因为它不是Number的子类,而且即使把list变量修改为List<? extends Object>类型也不能加入,就是因为编译器无法推断出泛型类型,加什么元素都是无效的.
在这种"写"操作的情况下,使用super关键字限定泛型类型的下界才可以.
import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Client { public static void main(String[] args) { } public static void write(List<? super Number> list) { list.add(123); list.add(3.14); } }
不用管是Integer类型还是Float类型,都可以加入到list列表中,因为它们都是Number累心g,zhejiu保证了泛型类的可靠性.
对于要限定上界还是下界,JDK的Collecctions.copy方法是一个非常好的例子,它实现了把源列表中的所有元素拷贝到目标列表中对应的索引位置上.代码如下:
1 public static <T> void copy(List<? super T> dest, List<? extends T> src) { 2 int srcSize = src.size(); 3 if (srcSize > dest.size()) 4 throw new IndexOutOfBoundsException("Source does not fit in dest"); 5 6 if (srcSize < COPY_THRESHOLD || 7 (src instanceof RandomAccess && dest instanceof RandomAccess)) { 8 for (int i=0; i<srcSize; i++) 9 dest.set(i, src.get(i)); 10 } else { 11 ListIterator<? super T> di=dest.listIterator(); 12 ListIterator<? extends T> si=src.listIterator(); 13 for (int i=0; i<srcSize; i++) { 14 di.next(); 15 di.set(si.next()); 16 } 17 } 18 }
源列表是用来提供数据的,所以src变量需要限定上界,带有extends关键字,目标列表是用来写入数据的,所以dest变量需要界定上界,带有super关键字.
如果一个泛型结构即用作"读"操作又用作"写"操作,那该如何进行限定呢?
不限定,使用确定的泛型类型即可,如List<E>.