跳至主要內容

extends_T和super_T的理解

cylin...大约 4 分钟

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类接收)。
  • ? 既不能用于方法参数传入,也不能用于方法返回