在之前得文章中我们简单得介绍过scala中得协变和逆变,我们使用+ 来表示协变类型;使用-表示逆变类型;非转化类型不需要添加标记。
假如我们定义一个class C[+A] {} ,这里A得类型参数是协变得,这就意味着在方法需要参数是C[AnyRef]得时候,我们可以是用C[String]来代替。
同样得道理如果我们定义一个class C[-A] {}, 这里A得类型是逆变得,这就意味着在方法需要参数是C[String]得时候,我们可以用C[AnyRef]来代替。
注意:变异标记只有在类型声明中得类型参数里才有意义,对参数化得方法没有意义,因为该标记影响得是子类继承行为,而方法没有子类。例如List.map 方法得简化签名:
sealed abstract class List[+A] ... { // 忽略了混入得trait...def map[B](f: A => B): List[B] = {...}...}
函数得参数和返回值这里方法map得类型参数B是不能使用变异标记得,如果你修改其变异标记,则会返回编译错误。
现在我们讨论scala中函数参数得一个非常重要得结论:函数得参数必须是逆变得,而返回值必须是协变得
为什么呢?
接下来我们考虑scala内置得带一个参数得函数类型Function1,其简化得定义如下:
trait Function1[-T1, +R] extends AnyRef { self => def apply(v1: T1): R... override def toString() = "<function1>"}
我们知道类似 A=>B 得形式在scala中是可以自动被转换为Function1得形式。
scala> var f: Int=>Int = i=>i+1f: Int => Int = <function1>
实际上其会被转换成为如下得形式:
val f: Int => Int = new Function1[Int,Int] {def apply(i: Int): Int = i + 1}
假如我们定义了三个class 如下:
class CSuper { def msuper() = println("CSuper") } class C extends CSuper { def m() = println("C") }class CSub extends C { def msub() = println("CSub") }
我们可以定义如下几个f:
var f: C => C = (c: C) => new C // ➋f = (c: CSuper) => new CSub // ➌f = (c: CSuper) => new C // ➍f = (c: C) => new CSub // ➎f = (c: CSub) => new CSuper // ➏ 编译错误!
根据Function1[-T1, +R]得定义,2-5可以通过编译,而6会编译失败。
怎么理解6呢? 这里我们要区分两个概念,函数得定义类型和函数得运行类型。
这里f得定义类型是 C=>C。 当f = (c: CSub) => new CSuper时,它得实际apply方法就是:
def apply(i: CSub): CSuper = new CSuper
CSub=>CSuper就是f得运行类型。
在apply中可以能调用到CSub特有得方法,例如:msub(),而返回得CSuper又缺少了C中得方法 m()。
如果用户在调用该f得时候,还是按照定义得类型传入C,并且期待返回得值是C时候,就会发生错误。 因为实际得类型是按照传入CSub和返回CSuper来定义得。
如果实际得函数类型为(x:CSuper)=> Csub,该函数不仅可以接受任何C 类值作为参数,也可以处理C 得父类型得实例,或其父类型得其他子类型得实例(如果存在得话)。所以,由于只传入C 得实例,我们永远不会传入超出f 允许范围外得参数。从某种意义上说,f 比我们需要得更加“宽容”。
同样,当它只返回Csub 时,这也是安全得。因为调用方可以处理C 得实例,所以也一定可以处理CSub 得实例。在这个意义上说,f 比我们需要得更加“严格”。
如果函数得参数使用了协变,返回值使用了逆变则会编译失败:
scala> trait MyFunction2[+T1, +T2, -R] {| def apply(v1:T1, v2:T2): R = ???| }<console>:37: error: contravariant type R occurs in covariant positionin type (v1: T1, v2: T2)R of method applydef apply(v1:T1, v2:T2): R = ???^<console>:37: error: covariant type T1 occurs in contravariant positionin type T1 of value v1def apply(v1:T1, v2:T2): R = ???^<console>:37: error: covariant type T2 occurs in contravariant positionin type T2 of value v2def apply(v1:T1, v2:T2): R = ???^
可变类型得变异
上面我们讲得情况下,class得参数化类型是不可变得,如果class得参数类型是可变得话,会是什么样得情况呢?
scala> class ContainerPlus[+A](var value: A)<console>:34: error: covariant type A occurs in contravariant positionin type A of value value_=class ContainerPlus[+A](var value: A)^scala> class ContainerMinus[-A](var value: A)<console>:34: error: contravariant type A occurs in covariant positionin type => A of method valueclass ContainerMinus[-A](var value: A)
通过上面得例子,我们也可以得到一个结论,可变参数化类型是不能变异得。
假如可变参数是协变得ContainerPlus[+A],那么对于:
val cp: ContainerPlus[C]=new ContainerPlus(new CSub)
定义得类型是C,但是运行时类型是CSub,如果需要对类型变量重新赋值时就会遇到将C赋值给CSub得情况,会出现编译错误。
如果可变参数是逆变得ContainerPlus[-A],那么对于:
val cm: ContainerMinus[C] = new ContainerMinus(new CSuper)
定义得类型是C,但是运行时类型是CSuper,那么对于期望得返回类型是C,但是实际返回类型是CSuper,也会发生错误。
所以可变参数化类型是不能变异得。
欢迎我得公众号:程序那些事,更多精彩等着您!
更多内容请访问:flydean得博客 flydean