Scala中协变和逆变主要作用是用来解决参数化类型的泛化问题。由于参数化类型的参数(参数类型)是可变的,当两个参数化类型的参数是继承关系(可泛化),那被参数化的类型是否也可以泛化呢?在Java中这种情况下是不可泛化的,然而Scala提供了三个选择,即协变、逆变和非变,解决了参数化类型的泛化问题。
协变和逆变
在Scala语言中,协变和逆变到处可见。如List,Queue等属于协变协变和逆变的一种。
协变和逆变使用“+”,“-”差异标记。当我们定义一个协变类型List[+A]时,List[Child]可以是List[Parent]的子类型,当我们定义一个逆变类型List[-A]时,List[Child]可以是List[Parent]的父类型
+B是B的超集,叫协变
-A是A的子集,叫逆变
假设有参数化特质List,那么可以有三种定义。如下所示:
(1) trait List [T]{}
非变。这种情况下,当类型S是类型A的子类型,则List [S]不可以认为是List [A]的子类型或父类型,这种情况和Java是一样的。
(2) trait List [+T]{}
协变。如果Sextends 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中如何定义协变逆变类呢,举例如下。
objectCovariantAndContravariantDemo {
defmain(args: Array[String]): Unit = {
//不变
definv1: Invariant[Tiger] = newInvariant[Tiger]()
//不可以赋值,编译器编译不通过
definv2: Invariant[Cat] = inv1
//协变
defcov1: Covariant[Tiger] = newCovariant[Tiger]()
//可以直接赋值
defcov2: Covariant[Cat] = cov1
//逆变
defcont1: Contravariant[Cat] = newContravariant[Cat]()
//可以直接赋值
defcont2: Contravariant[Tiger] = cont1
}
/**
* 定义一个不可变的类
*/
class Invariant[T] {}
/**
* 定义一个协变的类
*/
class Covariant[+T] {}
/**
* 定义一个逆变的类
*/
class Contravariant[-T] {}
/**
* 定义一个动物的类,具有行为(吃)
*/
class Animal {
def eat() {
println("Animal likeeat botany.")
}
}
/**
* 定义动物(猫)的类,具有行为(吃)
*/
class Cat extends Animal {
override def eat() {
println("Cat eatfish.")
}
}
/**
* 定义动物(老虎)的类,具有行为(吃)
*/
class Tiger extends Cat {
override def eat() {
println("Tiger eatmeat.")
}
}
}
上界和下界
类型的上界和下界,它们的含义如下。
1) U >: T
这是类型下界的定义,也就是U必须是类型T的父类(或本身,自己也可以认为是自己的父类)。
2) S <: T
这是类型上界的定义,也就是S必须是类型T的子类(或本身,自己也可以认为是自己的子类)
协变、逆变结合上下界
栗子
协变 | 逆变 |
trait c1[+T] { def method[K >: T](x:K) = x } | trait c1[-T] { def method [K <: T](x:K) = x } |
object c2 extends c1[Int]
c2.method (3) // 3 c2.method (3.0) // 3.0 c2.method ("abc") // "abc" | object c2 extends c1[Int]
c2.method (3) // 3 c2.method (3.0) // 报错 c2.method "abc") // 报错 |