18.6 协变,逆变和不变
参考文章: https://www.originate.com/thinking/stories/cheat-codes-for-contravariance-and-covariance/
Scala 阻止我们将一个指向 ArrayList[Int]
的引用赋值给一个指向 Array List[Any]
的引用。这是一件好事情。
通常来说,一个派生类型的集合不应该赋值给一个基类型的集合。
如果在期望接收一个基类实例的集合的地方,能够使用一个子类实例的集合的能力叫作
如果而在期望接收一个子类实例的集合的地方,能够使用一个超类实例的集合的能力叫作
18.6.1 定制集合的型变
我们也可以在定义集合类的时候控制这一行为,让集合类直接支持协变和逆变.
使用[+T]
支持协变, 使用[-T]
支持逆变. [T]
表示不变.
package day02.po
// Pet 类
class Pet(val name: String) {
override def toString: String = name
}
// Dog类, 并继承 Pet类
class Dog(override val name: String) extends Pet(name)
// 定义支持协变的类
class MyList1[+T] {}
// 定义支持逆变的类
class MyList2[-T] {}
object Test {
def main(args: Array[String]): Unit = {
var pets1: MyList1[Pet] = new MyList1[Pet]
var dogs1: MyList1[Dog] = new MyList1[Dog]
pets1 = dogs1 // 编译正确 协变
//------
var pets2: MyList2[Pet] = new MyList2[Pet]
var dogs2: MyList2[Dog] = new MyList2[Dog]
dogs2 = pets2 // 编译正确 逆变
}
}
在默认情况下,Scala 编译器将会严格检查型变。
我们也可以要求对协变或者逆变进行宽大处理。无论如何,Scala 都会检查型变是否可靠。
18.6.2 协变点和逆变点
所以, 如果一个类型即要出现在参数位置, 又要出现的返回值位置, 则应该使用不便
class Printer[+A] {
// 编译失败: covariant type A occurs in contravariant position in type A of value n
def print(n: A): Unit = {
println(n)
}
// 使用下界来解决
def print[U >: A](n: U): Unit = {
println(n)
}
}
class Printer[-A] {
// 编译失败: contravariant type A occurs in covariant position in type (n: A)A of method print
def print(n: A): A = {
println(n)
n
}
}
注意: 如果参数是函数, 则做为参数的函数的型变是反过来的.