Java-?extends T 与 ?super T

<? extends T>

类型的上界为T,具体类型可以是T or T的子类

image

对于以上类结构,做以下操作:

List<? extends Father> fatherList = new ArrayList<Father>(); //success
List<? extends Father> sonList = new ArrayList<Son>(); //success
List<? extends Father> humanList = new ArrayList<Human>(); //compile error

可以看到具体类型可以Father or Father的子类,如定义所述,父类将会编译报错,具体错误如下:

image

但是也正是由于 List<? extends Father>的具体类型不定,因此对于以下操作:

List<? extends Father> fatherList = new ArrayList<Father>();
List<? extends Father> sonList = new ArrayList<Son>();
fatherList.add(new Father()); //compile error
fatherList.add(new Son()); //compile error
sonList.add(new Son()); //compile error

List.add操作T or T的子类会编译报错:

image

这就很奇怪了,怎么感觉好像和定义有冲突啊,也和直觉有矛盾,既然具体类型可以是T or T的子类,为什么具体add操作时编译都报错?

仔细思考下,在上面例子中,List<? extends Father>意义为List可以存放的元素类型为Father or Father的子类,但这样意味着类型是不确定的;

这也和Java编译器处理有关:编译器在看到fatherList = new ArrayList<Father>()后,并没有限定List参数类型是Father,而是标上一个占位符:CAP#1,来表示捕获一个Father or Father的子类,具体类型不知道。然后无论是想add Son or Father,编译器都不知道能不能和这个CAP#1匹配,所以就都不允许。

即对于编译器来说,只知道这个声明:List<? extends Father> fatherList,明白fatherList可以存放的是Father or Father的子类,具体可能是放Father的、也可能是放Son的、…,但是真正是存放哪种类型的,编译器不知道,所以fatherList add任何类型都会编译报错,当然可以fatherList.add(null)是没有问题的,因为null可以是任何类型;

那么,根据上面的思路,虽然fatherList具体存放什么类型编译器是不知道的,但是编译器知道这个类型肯定是Father or Father的子类,那么,Father father = fatherList.get(0)肯定是没问题的,如下:

List<? extends Father> fatherList = new ArrayList<Father>(); //success
List<? extends Father> sonList = new ArrayList<Son>(); //success
Father father = fatherList.get(0); //success
Human human = fatherList.get(0); //success
Son son = (Son) sonList.get(0); //success

事实也是如此,既然Father father = fatherList.get(0)没问题,那么Human human = fatherList.get(0)更没问题了,注意,对于Son son = (Son) sonList.get(0)也符合具体语义。

综上所述,List <? extends T>不能add任何元素,但可以get操作以T or T的父类 接收。

既然List <? extends T>无法add任何元素,那有什么用?

这种形式的作用在于:规定list内元素的类型范围,让使用者知道其内元素是T or T的子类,在get操作时可以使用T接收,虽然无法add,但是可以初始化时赋值

List<? extends Father> sonList = getSonList();

<? super T>

类型的下界为T,具体类型可以是T or T的父类

image

<? extends T>相似的思路,以List<? supper T> list为例思考下:

按照定义,list存的元素类型是T or T的父类,因此有以下形式:

List<? super Father> fatherList = new ArrayList<Father>(); //success
List<? super Father> humanList = new ArrayList<Human>(); //success
List<? super Father> sonList = new ArrayList<Son>(); //compile error

可以理解,具体编译错误如下:

image

再来思考下listaddget操作。

编辑器不知道list具体会存什么类型,但是肯定是T or T的父类,因此listadd T or T的子类肯定没问题,list肯定能接收,但是add T的父类,肯定会问题:

List<? super Father> fatherList = new ArrayList<Father>(); //success
fatherList.add(new Father()); //success
fatherList.add(new Son()); //success
fatherList.add(new Human()); //compile error

符合事实,具体编译错误为:

image

对于list.get()操作,除了Object这个所有类的根基类之外,是没有哪个类型能够接收的。因为<? super T>只是控制了类型的下限,但是未控制上限,所以,任何类都没办法来接收这个类型,然而Java体系中Object是所有的根基类,因此,Object是可以接收的,但这样的话,元素的类型信息就全部丢失了,具体来看:

List<? super Father> fatherList = new ArrayList<Father>(); //success
Object object = fatherList.get(0); //success
Father father = fatherList.get(0); //compile error
Son son = fatherList.get(0); //compile error
Human human = fatherList.get(0); //compile error

具体编译错误为:

image

综上所述,List <? super T>能add T or T的子类,但get操作只能被Object接收。

总结

  • extends 可用于返回类型限定,不能用于参数类型限定

    ? extends T 只能用于方法返回类型限定,JDK能够确定此类的最小继承边界为T,只要是这个类的父类都能接收,但是传入参数无法确定具体类型,只能接受null的传入
  • super 可用于参数类型限定,不能用于返回类型限定

    ? supper T只能用于方法传参,因为JDK能够确定传入为T or T的子类,返回只能用Object类接收

PECS 原则

PECS(Producer Extends Consumer Super)原则:

  • 频繁往外读取内容的,适合用上界 extends
  • 经常往里插入的,适合用下界 super

<? extends T> 与 区别

E为限制类型,?通配符类型,<E extends T>表示后续都只能使用E进行某些判断或操作,而<? extends T>表示后续使用时可以是任意的。


参考:
Java中<? extends T>和<? super T>的理解#

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值