您的位置:首页 > 编程语言 > Java开发

浅谈Java泛型中的<? extends E>和<? super E>的区别

2017-09-23 21:50 741 查看
引入
再说这个之前,先来看一段代码:
假设有这么几个类及其继承关系,后面的例子也用这几个类作为基础示范
class People {
//人
}
class Man extends People {
//男人
}
class Woman extends People {
//女人
}
class Boy extends Man {
//男孩
}


可以看到,man是people的子类,那么:
 
List<People> list= new ArrayList<Man>();
     

是否可以编译通过?





很明显,编译的时候报错了。man是people的子类,ArrayList是List的子类,但并不代表List<Man>是List<People>的子类。所以便有了有限通配符
<? extends E>
<? extends E> 是 Upper Bound(上限) 的通配符,用来限制元素的类型的上限,比如:
List<? extends People> list_1 = null;

表示集合中的元素上限是People,即只能是People或People的子类,所以下面的赋值是合法的,编译时候不会报错:



但不能是其父类,否则编译时候就报错了:



接下来对其读写数据进行了解:
1、读
不管给该list_1如何赋值,可以保证的是里面存放的一定是People或People的子类,编译器可以确定获取的是People类型,所以可以直接从该集合中读取到People类型。即读取是允许的。



2、写



People、Man、Woman都是People类或其子类,但是这里却是编译错误的。原因是? extends People>仅仅告诉编译器该集合元素类型上限是People,这里编译器并不能确定具体的类型,即它可能实际指向了Man,但是你却add一个Woman类型,所以这里编译器不允许这么做。
<? super E>
<? super E> 是 Lower Bound(下限) 的通配符 ,用来限制元素的类型下限,比如:
List<? super Man> list_4 = null;

该表示给出了集合中元素的下限是Man,即只能为Man或者Man的父类,而不能是Man的子类,如下:



接下来对其读写数据进行了解:
1、读
允许从该集合获取元素,但是无法保证里面存放的是Man或者是Woman,唯一可以确定的是存放的是Object或其子类,而无法确定具体的类型。



这样都没错,但是实际用的时候还是要注意,像这样获取Woman可能导致异常。
2、写
可以确定的是集合中的元素一定是Man或Man的子类,所以添加Man或Boy都是正确的,但是不能添加非Man的子类:



使用场景
很多时候都是用它来当作方法中的形参。
这里先了解下PECS法则
1、PECS
PECS指“Producer Extends,Consumer Super”。换句话说,如果参数化类型表示一个生产者,就使用<? extends T>;如果它表示一个消费者,就使用<? super T>
2、例子
这里使用网上常见的例子水果来说明,有如下关系:



假设此时有个水果供应商Produce,
class Produce<E> {
public void produce(List<E> list) {
for (E e : list) {
//生产...
System.out.println("批量生产...");
}
}
}


它主要销售水果
Producer<Pear> p = new Produce<>();
List<Pear> pears = new ArrayList<Pear>();
p.produce(pears);

这样并没有什么问题。但是万一他突然想换成销售苹果了,此时:



这样就会发现,编译并不能通过,因为List<E>已经在初始化时确定为Pear了,而不再兼容Appler类型,即使你最开始使用的是Produce<Fruit>,即方法produce的参数list为List<Fruit>,虽然Apple和Pear是Fruit的子类,但是由上面的引入知识知道,List<Fruit>并不是List<Apple>的父类,即这样也是行不通的,所以这里就需要使用List<? extends E> list了。
修改后如下:
class Producer<E> {
public void produce(List<? extends E> list) {
for (E e : list) { //利用<? extends E>读取的特性
//生产...
}
System.out.println("批量生产完成...");
}
}


此时只要供应商new的时候为Fruit,则生产的货物只要为Fruit或其子类即可,所以Pear和Apple都可通过。如下:



接着举一个消费者的例子(可能例子举得不是很好)
//消费者
class Consumer<E> {
public E consume(List<E> list) {
E e = list.get(0); //模拟消费一个(感觉用队列比较合适)
return e;
}
}

每次消费者都从一个list中消费一个。加入有一个红苹果消费者:



这里是没什么问题的,但是红苹果也是苹果,如果这样呢:



这时候,<? super E>派上用场了。
//消费者
class Consumer<E> {
public E consume(List<? super E> list) {
E e = (E) list.get(0); //模拟消费一个(感觉用队列比较合适)
return e;
}
}

此时再按刚才的操作:



编译并不会出问题了。

其实,在java提供的许多类库里就有用到了,比如Collections的静态方法copy:



为了保证在list复制过程中类型的安全,限制了原list的上限,保证了目标数组的下限。

参考链接:https://www.cnblogs.com/wangmingshun/p/5389341.html
                   http://blog.csdn.net/asdfsadfasdfsa/article/details/52794573
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: