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

引入
再说这个之前,先来看一段代码:
假设有这么几个类及其继承关系,后面的例子也用这几个类作为基础示范
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的上限,保证了目标数组的下限。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值