2021-03-20 泛型与逆变、协变、不变笔记

目录

泛型与逆变、协变、不变

1. 数组是协变的

2. 未使用通配符的泛型类是不变的(为何泛型类无法强转)

3. 使用通配符的泛型类可逆变可协变(泛型类强转解决方案)


泛型与逆变、协变、不变

假设有Parent、Son、GrandSon三个类

class Parent{}

class Son extends Parent{}

class GrandSon extends Son{}

1. 数组是协变的

由于Son可以向上转型为Parent , 所以Son[] 可以向上转型成Parent[] ,形如这种A>B,则f(A) > f(B),就叫协变,我们都变了,但是关系不会变

会发现编译器并不会报错
由于 Parent parent = (Parent)new Son();
所以 Parent[] arr = (Parent[]) new Son[5];

虽然一旦运行起来,arr[2] = new Parent() 会抛出ArrayStoreException异常
但以下三个在编译期间均不会报错
arr[0] = new GrandSon();
arr[1] = new Son();
arr[2] = new Parent();

2. 未使用通配符的泛型类是不变的(为何泛型类无法强转)

由于Son可以向上转型为Parent,但是List<Parent> parents = (List<Parent>)new ArrayList<Son> 直接编译期报错,同时List<Son> sons = (List<Son>)new ArrayList<Parent>同样直接编译期报错,这种A>B,但f(A)和f(B)之间完全没有任何关系,就叫不变

Parent obj = (Parent)new Son();

但以下两个编译期直接报错
List<Parent> parents = (List<Parent>)new ArrayList<Son>();
List<Son> sons = (List<Son>)new ArrayList<Parent>();

所以泛型类无法强转

3. 使用通配符的泛型类可逆变可协变(泛型类强转解决方案)

由于Son可以向上转型为Parent,且 List<? extends Parent> list = (List<? extends Parent>)new ArrayList<Son>()  ,符合A>B,则f(A) > f(B),所以通配符泛型可以是协变的。

但是所有add操作都会报错,因为? extends Parent表示泛型可以是Parent的任何子类,一个父类可以有无限个子类,所以List无法确定究竟该接收哪个,所以选择全报错。

Parent obj = (Parent)new Son();

发现这样写并不报错
List<? extends Parent> list = (List<? extends Parent>)new ArrayList<Son>();

但是所有添加行为都报错 
list.add(new Son());
list.add(new GrandSon());
list.add(new Parent()); 

List<?> list = new ArrayList<>();
同样所有添加行为都报错
list.add(new Son());
list.add(new GrandSon());
list.add(new Parent()); 

由于Son可以向上转型为Parent,且List<? super Son> superList = (List<? super Son>)new ArrayList<Parent>(),符合A>B,则f(B) > f(A),所以通配符泛型可以是逆变的。只要add(Son以及Son的子类)就不会报错,因为一个类只有有限的父类,可以清晰的知道可以传入哪些子类,所以不会报错。

List<? super Son> superList =(List<? super Son>) new ArrayList<Parent>();

superList.add(new Parent()); // 编译报错
superList.add(new Son()); // 不报错
superList.add(new GrandSon()); // 不报错

所以

1. 如果想要强转,需要通过通配符

2. 如果强转的过程中,需要对传入泛型对象进行修改,需要遵循psce原则(有类似add、set等操作,需要使用List<? super Son>;有类似于get的操作,使用List<?extend Son>)

public static void main(String[] args) {
    List<Son> sons = new ArrayList<>();
    List<Parent> parents = forceCast(sons);
}

private static <T> List<Parent> forceCast(List<?> list) {
    return (List<Parent>)list;
}

psce原则:
1. 生产者,即set或add,需要用super
2. 消费者,即get,需要用extend
3. 该方法表明,从Son或Son子类列表中取,加到Parent列表中
private static <T> void transfer(List<? super Son> dest, List<? extend Son> from) {
    Son son = from.get(0);
    dest.add(son)
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值