小白话 泛型的协变和逆变

415 阅读1分钟
先出一张类图

截屏2021-10-18 下午4.37.56.png

协变:
Java 协变<? extends T>:上界通配符 ?extends
kotlin 协变 只能用于出参
上界的概念:这里以D为界限,上界通配符限制的D及其子类,顺着D往下找
示例1:代码是正常可以编译通过的,因为listD 是限制了上界 类型是D,可以是List/List/List 等
List<? extends D> listD = new ArrayList<>();
List<E> listE = new ArrayList<>();
listD = listE;
示例2:这里listD 的类型由于可以是 List/List/List,但是在编译期间无法确定具体的类型,所以添加D时 是报错的,这里也就解释了为什么只能用作返回值,而不能用作入参
List<? extends D> listD = new ArrayList<>();
listD.add(new D());//报错
逆变
java 逆变 <? super T> : 下界通配符
Kotlin 逆变 只能用于入参

下届的概念:这里以D为界限,下届通配符限制的D及其父类,顺着D往上找

示例1:这里是正常编译通过的,listD 使用了下界通配符,所以这里的listD 可以是 List /List
List<B> listB = new ArrayList<>();
List<? super D> listD = new ArrayList<>();
listD = listB;
示例2:最后一行添加B报错,这里的理解: listD 限制了类型为D的父类,但是这里并不知道D的父类有哪些,可以是List,也可以是List,所以这里添加B 的时候报错
List<? super D> listD = new ArrayList<>();
listD.add(new D());
listD.add(new E());
listD.add(new B());//报错
示例3: 由于listD的不确定性,listD 里的元素也不确定,获取的时候只能返回object,但是可以强转
List<? super D> listD = new ArrayList<>();
listD.add(new D());
Object o = listD.get(0);
if( o instanceof  D){
    System.out.println("可以获取");
}
原则
1.从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)
2.如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
3. 如果既要存又要取,那么就不要使用任何通配符