<? extends someclass>为什么可以get而不能set?
void set(? extends someclass);
? extends someclass get();
我们使用通配符通常是希望某一个实例可以向上转型 例:
List<Animal> list = new ArrayList<Dog>() //compile error
List<? extends Animal> list = new ArrayList<Dog>() //compile ok
这些是基本的概念,在这儿我就不再概述为什么了,我们要讲的是为什么上界限定(? extends someclass)使用set方法
编译错误,而使用get方法却是安全的呢?
0)现在假想我们有一个Animal基类和若干子类(这些子类都继承自Animal)
让我们先考虑一下数组,假如你以如下的形式建立了一个数组.
Animal[] ani = new Dog[](10); //compile ok
利用了向上转型的多态思想,这是完全合法的 现在我们设想向里面添加元素。
ani[0] = new Dog() //compile ok ,runtime ok
ani[1] = new Animal() //compile ok , runtime error
我们知道现在操纵的是Dog类型的数组,但是编译器不知道(只有在运行时,ani引用才和dog数组建立联系),编译的时候编译器没有理由会拒绝存放Animal类型的对象,因为他本身就是一个Animal类型的引用。
数组拥有特殊的保护机制,即使在编译时期插入了错误的对象,运行时也能发现并抛出这些异常,所以也没必要为此担心什么。
1)带通配符的set方法
现在假设你运用的是泛型,你能不能这样做呢? 现在考虑如下例子
List<? extends Animal> list = new ArrayList<Dog>() //compile ok
这也是向上转型。
但有一点和数组不同的是,数组在编译阶段就确定了他是什么类型的数组引用(如上例是Animal类型的数组引用),而带泛型的List呢?他只知道他是一个某类型的List引用,只知道这个“某类型”是继承自Animal的类,具体是Dog,Cat,还是Monkey呢,他一无所知。
你可能会提出疑问,后面不是跟了 new ArrayList<Dog>() 吗,这不是在告诉他他是一个Dog类型的ArrayList引用吗?很遗憾,编译器没有那么聪明,这些只会在运行时建立联系,然而运行时的类型擦除又让他们携带的信息烟消云散,你也就无法期待这些东西在运行时会给你什么惊喜了。
那如果想向我们刚建好list里面添加元素呢 如add(new Dog()) 或者add(new Animal()) (和set一样,在此以add为例更好)
list.add(new Dog()) //compile error
list.add(new Animal()) //compile error
编译器拒绝一切为其添加元素的操作(传参操作),为什么?因为 List<? extends Animal> list 无法确定他具体是什么类,就像List<Animal>只能为其添加Animal及Animal的子类元素一样,你一个连具体是什么类都不知道的引用,编译器自然认为任何添加都是不安全的。
让我们再举个容易理解的例子,我们暂时仅仅只观察这个式子-------List<? extends Animal> 现在我们假设?是Cat,那么我们能做些什么?
我们能向里面添加 Cat和Cat的子类元素,但是我们不能添加Dog,monkey甚至是Animal元素。
那现在返回来,编译器无法得知 ? 具体是什么类型,那 ?可能是Cat,也可能是Dog 或者其他的继承自Animal的类,我们又怎么能确定哪些add是安全的,哪些add是非法的呢?
所以我们自然无法使用add或者set方法。
2) get方法的合理性
test.add(new Cat());
List<? extends Animal> list = test;
Animal ani = list.get(0); //compile ok,runtime ok