以java为例理解协变性

以java为例理解协变性


这篇文章以java为例,解释下语言的类型系统中的几个重要概念,协变性(covariance)、逆变性(contravariance)和无关性(invariant)

在面向对象语言中由于继承的存在,安全的类型转换对于书写正确的代码至关重要。而上面几个概念就是用来描述这种类型转换的性质的。

首先,看下这几个概念的定义:


如果M和N是类型,f()表示类型转换,≤表示子类型关系,(例如M≤N,表示M是N的子类)那么:

如果M≤N 则f(M) ≤f(N) 那么 f()是协变的

如果M≤N 则f(N) ≤f(M) 那么 f()是逆变的

如果上面两种都不成立,那么f()是无关的


举个例子,首先规定如下继承规则,后面例子中不另行说明都默认有这个定义 C≤B≤A

class A{}
class B extends A{}
class C extends B{}

 然后,规定f(x)=x[]

 

B[] w2 = null;
A[] q1 = w2;
B[] q2 = w2;
C[] q3 = w2;//ERROR:编译错误Type mismatch
 

由上可得,B[]为A[]的子类,这样就证明数组变换具有协变性

 这说明在java中,数组具有协变性的

 再看个例子

class Animal{
public B deal(){return null;}
}
 
class Cat extends Animal{
@Override
public B deal(){return null;}
}

Cat继承了deal()函数,deal()的返回值为A则会发生编译错误,返回为B和C则正确。这说明继承覆盖函数返回值具有协变性。

 那对于继承覆盖函数的参数呢?

class Animal
{
void deal(B t){}
}
 
class Cat extends Animal
{
@Override
void deal(B t){}
}

Cat的deal函数为A或者C都会报错,这说明继承覆盖函数的参数具有不变性。当然,去掉Override标志后,函数重载deal函数,参数写什么都无所谓,但是那样连类型转换都没有发生,就没有讨论的价值了。

 几个基本的情况都清楚了,下面看下java的泛型List<?>

List<B> t1 = null;
List<B> t2 = null;
t1 = t2;


很明显,t2的类型为List<A>,List<C>都会报编译错误

这说明List<>和数组不同,具有不变性!

所幸,对于这种尴尬的情况,java提供了通配符这一功能,来解决这一问题,extends和super,前一个代表了协变性,后一个代表逆变性。

         List<A>t1 = null;
         List<B>t2 = null;
         List<C>t3 = null;
         List<?extends B> t = null;
         t = t1;//ERROR
         t = t2;
         t = t3;
 
         List<A>t1 = null;
         List<B>t2 = null;
         List<C>t3 = null;
         List<?super B> t = null;
         t = t1;
         t = t2;
         t = t3;//ERROR
 


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值