以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 t1 = null;
List t2 = null;
t1 = t2;


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

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

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

         Listt1 = null;
         Listt2 = null;
         Listt3 = null;
         List t = null;
         t = t1;//ERROR
         t = t2;
         t = t3;
 
         Listt1 = null;
         Listt2 = null;
         Listt3 = null;
         List t = null;
         t = t1;
         t = t2;
         t = t3;//ERROR
 


 

你可能感兴趣的:(以java为例理解协变性)