<? extends T>
类型的上界为T,具体类型可以是T or T的子类;
对于以上类结构,做以下操作:
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的子类,如定义所述,父类将会编译报错,具体错误如下:
但是也正是由于 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的子类会编译报错:
这就很奇怪了,怎么感觉好像和定义有冲突啊,也和直觉有矛盾,既然具体类型可以是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的父类;
与<? 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
可以理解,具体编译错误如下:
再来思考下list
的add
及get
操作。
编辑器不知道list
具体会存什么类型,但是肯定是T or T的父类,因此list
add 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
符合事实,具体编译错误为:
对于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
具体编译错误为:
综上所述,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>
表示后续使用时可以是任意的。