我所认识的java泛型(主要讨论通配符的使用)
在使用java泛型的时候,经常会遇到类似的声明<?extends XXX>或者<? super XXX>,XXX代表一个类或接口。这到底是什么意思呢?今天看了些书有了一点儿体会,不知恰当与否,忘讨论之。
首先需要清楚一个事实,在java中上转型是安全的,下转型是不安全的。例如:String类是Object类的子类,我们可以将String强转为Object但是却不能将Object类强转为一个String,这很明显,因为在String类中包含了Object类没有的方法;
再来看一个事实,在java中泛型是具有不可变性的,何为不可变性,那先来看看什么是可变(协变)。一个数组String [] strings = new String[10];,让strings的值付给一个Object数组:Object [] objects = strings;,这是可以行的通的,因为数组的协变性,String是Object的子类,而这却是存在隐患的,比如我现在写下如下代码:objects[0] = new Integer(1);这也是允许的,编译器会通过编译而没有警告,但是在运行时就会抛出如下异常:java.lang.ArrayStoreException,原因自然是我们在一个String类型的数组中存储了int类型的元素。但是在编译时期编译器是无法检查出来的,所以这是一个不安全的隐患。
说完了数组的协变,那么接下来说泛型的不变性,来说一个例子:List<String>与List<Object>之间是什么关系?是父子类的关系吗?答案应该是否定的,我们不能写出如下代码:List<String> strings = new ArrayList<>();List<Object> objects = strings;因为上述两者之间是没有父子类的关系的,这个性质称为泛型的不变性与数组的协变性是相对的。这个性质避免了泛型也出现类似数组那样的不安全的表现,在编译时期就可以将错误检查出来。
而这性质有时候也会使得程序写起来有些不灵活,在effect java中有一个例子,大概是个样子的。实现了一个栈,大概代码如下:
public class Stack<E> { private E [] elements; public void push(E e){ ..... } public E pop(){ ..... } // 将src中的所有元素进栈 public void pushAll(Iterable<E> src){ ..... } // 将栈中的所有元素出栈道dst中 public void popAll(Iterable<E> dst){ ..... } }
仅仅表示了代码的大概意思,细节没有体现出来。现在我们有一个Stack<Number> stack = new Stack<>();类型的栈,我想将Integer类型的Iterable<Integer> integers通过pushAll方法放入栈中,这是不能实现的,虽然Integer是Number的子类,但是Iterable<Number>和Iterable<String>是没有关系的,这个时候就需要用到<? extends XXX>了。这句话的含义是任何XXX的子类,但是具体是什么子类是不确定的, 但是我知道了Iterable<? extends Number>中的?表示的是任何Number的子类,知道了这一点就可以将其中的元素向上转型为Number这是允许的,所以我们可以通过某种get机制得到Iterable<? extends Number>中的元素,但是却不能通过什么set机制将元素放入其中,因为我们无法知道这个?号到底代表的是什么类型。修改下pushAll方法,将其参数修改为pushAll(Iterable<? extends E> src),这样我们就可以将类型为Iterable<Integer>的参数放入pushAll中去了。在effect java中称src为生产者,因为提供了数据源。同样来看看代码中的另外一个方法popAll,如果我写出如下代码:
Stack<Number> stack = new Stack<>();Iterable<Objcet> dst = ...;stack.popAll(dst);这样的代码从逻辑上来看,我将Number类放入一个Object类的容器中,这个是可以说通的,因为Object是Number的父类。但是代码却不能通过编译,因为,Iterable<Objcet>与Iterable<Number>的关系是没有关系,那么这个时候就需要<? super Number>了,这个?代表任何Number的父类,这个父类到底是什么呢?我们不知道,但是我们知道任何Number的子类是能放入其中的,因为Number的子类总能通过向上转型为Number,所以<? super Number>的容器可以通过set方法进行填充,仅仅要求是Number的子类;但是却无法通过某种get方法将其值返回,因为我不知道到底?代表的是什么父类,而我也不能通过向下转型将父类转成Number类。下面修改代码中的popAll方法,将其参数改为popAll(Iterable<? super E> dst),这样我就可以将Stack<Number>中的元素放入dst中了,这dst成为消费者,因为Number是Object的一个子类。
上述观点只是自己的一点儿想法,希望指正,不吝赐教。^_^