Scala中协变和逆变主要作用是用来解决参数化类型的泛化问题。由于参数化类型的参数(参数类型)是可变的,当两个参数化类型的参数是继承关系(可泛化),那被参数化的类型是否也可以泛化呢?在Java中这种情况下是不可泛化的,然而Scala提供了三个选择,即协变、逆变和非变,解决了参数化类型的泛化问题。
协变和逆变
在Scala语言中,协变和逆变到处可见。如List,Queue等属于协变和逆变的一种。
协变和逆变使用“+”,“-”差异标记。当我们定义一个协变类型List[+A]时,List[Child]可以是List[Parent]的子类型,当我们定义一个逆变类型List[-A]时,List[Child]可以是List[Parent]的父类型。
+B是B的超集,叫协变+B是B的超集,叫协变
-A是A的子集,叫逆变
假设有参数化特质List,那么可以有三种定义。如下所示:
(1) trait List [T]{}
非变。这种情况下,当类型S是类型A的子类型,则List [S]不可以认为是List [A]的子类型或父类型,这种情况和Java是一样的。
(2) trait List [+T]{}
协变。如果S extends A (S为子类型,A为父类型),则List [S]为子类型,List [A]为父类型 S <: A => List [S] <: List [A]。
(3) trait List [-T]{}
逆变。如果S extends A (S为子类型,A为父类型),则List [S]为父类型,List [A]为子类型,和协变互逆S <: A => Queue[S] >: Queue[A]。
那么,在Scala中如何定义协变逆变类呢,举例如下。
package first
//协变逆变测试
object CovariantAndContravariantDemo {
def main(args: Array[String]): Unit = {
//不变
//定义一个方法,返回值为Invariant[Child]
def inv1:Invariant[Child]=new Invariant[Child]()
// 会报错:因为Invariant是不变的,所以虽然Parent是Child的父类也不能赋值
def inv2:Invariant[Parent] = inv1
//协变
//定义一个方法,返回值为Covariant[Child]
def cov1:Covariant[Child]=new Covariant[Child]()
//不会报错:Covariant是协变的,所以当Child是Parent的子类的时候,Covariant[Child]
//也可以是Covariant[Parent]的子类。【这就是协变的强大之处,java没有这个功能】
def cov2:Covariant[Parent] = cov1
//逆变
//定义一个方法,返回值为Cotravariant[Parent]
def cot1:Cotravariant[Parent]=new Cotravariant[Parent]()
//不会报错:Cotravariant是逆变的,所以当Child是Parent的子类的时候,Cotravariant[Parent]
//也可以是Cotravariant[Child]的子类型,赋值不会报错。
def cot2:Cotravariant[Child] = cot1
}
}
/**
* 定义一个不可变的类
*/
class Invariant[T]{}
/**
* 定义一个协变的类
*/
class Covariant[+T]{}
/**
* 定义一个逆变的类
*/
class Cotravariant[-T]{}
/**
* 定义一个父类
*/
class Parent{}
/**
* 定义一个子类
*/
class Child extends Parent{}
相信我上面的例子很详细!