extends_T和super_T的理解
1. 区别
- ? extends T:表示某个未知类型,该类型是 T 类型的子类(包括 T 自身)。换句话说,
? extends T
表示的是一个泛型的上界,用于限定泛型类型的上限 - ? super T:表示某个未知类型,该类型是 T 类型的父类(包括 T 自身)。换句话说,
? super T
表示的是一个泛型的下界,用于限定泛型类型的下限
2. extends T 不能往里存,只能往外取
public class test {
public static void main(String[] args) {
List<? extends Father> list = new LinkedList<>();
list.add(new Son()); //error
}
}
class Human{
}
class Father extends Human{
}
class Son extends Father{
}
class LeiFeng extends Father {
}
list.add(new Son());这行会报错:
'java.util.List' 中的 'add(capture<? extends cn.cestc.Father>)' 无法应用于 '(cn.cestc.Son)'
List<? extends Father>
表示 “具有任何从Son继承类型的列表”,编译器无法确定List所持有的类型,所以无法安全的向其中添加对象
或许想尝试这么做:
List<? extends Father> list = new LinkedList<Son>();
list.add(new Son());
即使你指明了为Son类型,也不能用add方法添加一个Son对象。
2.1 list中为什么不能加入Father类和Father类的子类呢??
List<? extends Father>
表示上限是Father,下面这样的赋值都是合法的
List<? extends Father> list1 = new ArrayList<Father>();
List<? extends Father> list2 = new ArrayList<Son>();
List<? extends Father> list3 = new ArrayList<LeiFeng>();
如果List<? extends Father>
支持add方法的话:
- list1可以add Father和所有Father的子类
- list2可以add Son和所有Son的子类
- list3可以add LeiFeng和所有LeiFeng的子类
下面代码是编译不通过的:
list1.add(new Father());//error
list1.add(new Son());//error
原因是编译器只知道容器内是Father或者它的派生类,但具体是什么类型不知道
可能是Father?可能是Son?也可能是LeiFeng
编译器在看到后面用Father赋值以后,集合里并没有限定参数类型是“Father“。而是标上一个占位符:CAP#1,来表示捕获一个Father或Father的子类,具体是什么类不知道,代号CAP#1。然后无论是想往里插入Son或者LeiFeng或者Father编译器都不知道能不能和这个CAP#1匹配,所以就都不允许
2.2 错误原因
所以通配符<?>
和类型参数的区别就在于,对编译器来说所有的T都代表同一种类型。比如下面这个泛型方法里,三个T都指代同一个类型,要么都是String,要么都是Integer
public <T> List<T> fill(T... t);
但通配符<?>
没有这种约束,List<?>
单纯的就表示:集合里放了一个东西,是什么我不知道
所以这里的错误就在这里,List<? extends Father>
里什么都放不进去
2.3 用法
由于我们已经保证了List中保存的是Father类或者他的某一个子类,所以,可以用get方法直接获得值:
List<? extends Father> list1 = new ArrayList<>();
Father father = list1.get(0); //读取出来的东西只能存放在Father或它的基类里
Object object = list1.get(0); //读取出来的东西只能存放在Father或它的基类里
Human human = list1.get(0); //读取出来的东西只能存放在Father或它的基类里
2.4 总结
在 List<? extends Father>
这种情况下,你不能直接向列表中添加元素,因为编译器无法确定实际类型是什么。List<? extends Father>
表示的是一个泛型列表,该列表中的元素类型是 Father
类型或 Father
的子类,但编译器无法确定具体是哪个子类。
如果你尝试向这样的列表中添加元素,编译器会报错。这是因为,如果列表中的元素类型是 Father
的某个子类,而你尝试添加的对象类型可能不是这个子类,这样会破坏类型安全性
3. <? super T>不影响往里存,但往外取只能放在Object对象里
下界用super进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至Object
//super只能添加Father和Father的子类,不能添加Father的父类
//读取出来的东西只能存放在Object类里
List<? super Father> list = new ArrayList<>();
list.add(new Father());
list.add(new Human()); //compile error
list.add(new Son());
Father person1 = list.get(0); //compile error
Son son = list.get(0); //compile error
Object object1 = list.get(0);
4. PECS原则
最后看一下什么是PECS(Producer Extends Consumer Super)原则,已经很好理解了:
- 频繁往外读取内容的,适合用上界Extends
- 经常往里插入的,适合用下界Super
5. 总结
extends
可用于返回类型限定,不能用于参数类型限定(换句话说:? extends xxx
只能用于方法返回类型限定,jdk
能够确定此类的最小继承边界为xxx
,只要是这个类的父类都能接收,但是传入参数无法确定具体类型,只能接受null
的传入)super
可用于参数类型限定,不能用于返回类型限定(换句话说:? supper xxx
只能用于方法传参,因为jdk
能够确定传入为xxx
的子类,返回只能用Object
类接收)。?
既不能用于方法参数传入,也不能用于方法返回