【Java】协变与逆变

一、定义

若类A是类B的子类,则记作A ≦ B。设有变换f(),若:

  • 当A ≦ B时,有f(A)≦ f(B),则称变换f()具有协变性。
  • 当A ≦ B时,有f(B)≦ f(A),则称变换f()具有逆变性。
  • 当A ≦ B时,f(A)与f(B)无关,则称变换f()具有不变性。

二、数组协变,泛型不变

1. 数组的协变性

设有Super和Sub两个类,且Sub继承自Super

public class Super{}
class Sub extends Super{}

则如下代码在Java中是允许的:

Super[] sups = new Sub[];

这里的f()就是从类延伸到数组的变换,而原有的继承关系不变,所以说Java的数组是协变的。
看似合理的语言设计,其实是存在一些漏洞的。考虑下面的代码:

Object[] objs = new Integer[10];
objs[0] = "afly";

很不合理吧?但是上面的代码在编译时没有报错,只会在运行期抛出ArrayStoreException。这就是数组协变带来的静态类型漏洞:编译期无法完全保证类型安全。看上去Java的设计者是在程序的易用性与类型安全之间做了取舍,因为如果不支持数组协变,一些通用的方法如Arrays.sort(Object[])确实没办法正常工作。

2. 泛型的不变性

依然以Super和Sub为例。下面两行代码在Java中是不允许的:

List<Super> supList = new LinkedList<Sub>();  //error
List<Sub> subList = new LinkedList<Super>();  //error

可以看出,Java的泛型具有不变性。但是泛型的不变性也会带来使用上的不灵活,为此,Java使用有界类型使得泛型可以支持协变与逆变:

List<? extends Super> list = new ArrayList<Sub>();    //允许,协变性
List<? super Sub> list = new ArrayList<Super>();      //允许,逆变性

3. 泛型有界的副作用

class A {}
class B extends A{}
class C extends B{}
<? extends T>

<? extends T> a, a这个变量可以接受 T 及其 T 子类的集合,上界为 T,并且从 a 取出来的类型都会被强制转换为 T

        List<A> as = new ArrayList<>();
        List<B> bs = new ArrayList<>();
        List<C> cs = new ArrayList<>();

        // 可以通过编译
        List<? extends B> b1 = cs;
        // 不能通过编译,只能接受B及其子类的集合
        List<? extends B> b2 = as;

        // 重点注意:下面三行都不能通过编译
        b1.add(new A());
        b1.add(new B());
        b1.add(new C());
        // 重点注意:可以通过编译
        b1.add(null);

<? extends T>最需要注意的是,就是不能向里面添加除null之外的其他所有元素

<? super T>

<? super T>,它和 <? extends T> 有点相反。对于 <? super T> a,a 这个变量可以接受 T 及其 T 父类的集合,下界为 T,并且从 a 取出来的类型都会被强制转换为 Object

        List<A> as = new ArrayList<>();
        List<B> bs = new ArrayList<>();
        List<C> cs = new ArrayList<>();

        // 可以通过编译
        List<? super B> b1 = as;
        // 不能通过编译,只能接受B及其父类的集合
        List<? super B> b2 = cs;

        // 重点注意:不能通过编译,只能添加 B 及其 B 的子类
        b1.add(new A());
        // 重点注意,可以通过编译
        b1.add(new B());
        b1.add(new C());
        b1.add(null);

注意,<? super T>最需要注意的是,在虽然可以接受 T 及其父类的赋值,但是只能向里面添加 T 及其 T 的子类。

总结
  1. List<? extends T> a ,可以把 a 及其 a 的子类赋给 a,从 a 里取的元素都会被强制转换为 T 类型,不过需要注意的是,不能向 a 添加任何除 null 外是元素。

  2. List<? super T> a ,可以把 a 及其 a 的父类赋给 a,从 a 里取的元素都会被强制转换为 Object 类型,不过需要注意的是,可以向 a 添加元素,但添加的只能是 T 及其子类元素。

3. PECS原则

PECS(Producer-Extends Consumer-Super)

  1. 频繁往外读取内容的,适合用上界Extends
  2. 经常往里插入的,适合用下界Super
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值