以java为例理解协变性

来源:互联网 发布:蛮牛unity3d 编辑:程序博客网 时间:2024/05/19 06:51

以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{@Overridepublic B deal(){return null;}}

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

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

class Animal{void deal(B t){}} class Cat extends Animal{@Overridevoid 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 


 

原创粉丝点击