关于Java的协变和逆变

逆变与协变:如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类):

  1. f(⋅)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立;
  2. f(⋅)是协变(covariant)的,当A≤B时有f(A)≤f(B)成立;
  3. f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。

1.数组的协变性

在Java中数组是具有协变性的,如果B是A的子类型,则B[]是A[]的子类型(f(⋅)映射为数组),即子类型的数组可以赋予父类型的数组进行使用,但数组的类型实际为子类型。例如:

Fruit[] fruits = new Apple[10]; // subclass of fruits
fruits[0] = new Apple();
fruits[1] = new RedFujiApple(); // subclass of Apple

这里fruits所引用的数组其实是Apple[]类型。

从协变数组读取元素是完全安全的,无论是编译期还是运行时,都不会发生任何问题:

Fruit fruit = fruits[0]; // return an Apple, which is the subclass of Fruit

但是将Fruit类型以及其子类型的元素写入到协变数组fruits中是有可能在运行时出现问题的,因为Apple类型无接受Fruit类型和其它非Apple的子类型(编译器无法检查):

fruits[0] = new Fruit(); // java.lang.ArrayStoreException
fruits[0] = new Orange(); //subclass of Fruit, java.lang.ArrayStoreException

这是Java数组的“缺陷”,在利用数组的协变性时,应该尽量把协变数组当作只读数组使用。

2.泛型中的协变和逆变

Java中泛型是不变的,但可以通过通配符"?"实现协变和逆变:

  1. <? extends>实现了泛型的协变:
List<? extends Number> list = new ArrayList<Integer>();
  1. <? super>实现了泛型的逆变:
List<? super Integer> list = new ArrayList<Number>();

由于泛型的协变只能规定类的上界,逆变只能规定下界,使用时需要遵循PECS(producer-extends, consumer-super): 要从泛型类取数据时,用extends; 要往泛型类写数据时,用super; 既要取又要写,就不用通配符(即extends与super都不用)。

3.函数替换:
函数f可以安全替换函数g,要求函数f接受更一般的参数类型,返回更特化的结果类型(输入类型是逆变,输出类型协变)。
由LSP原则(Liskov Substitution Principle),即所有引用父类型的地方必须能透明地使用其子类型的对象。为了安全的替换替换函数g,我们需要用原有参数类型或其父类接受客户的传入,返回原有类型的子类。以此遵循对应的规格说明。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值