11---
22layout : tour
3- title : Variance
3+ title : 型变
44
55discourse : false
66
@@ -13,3 +13,143 @@ language: zh-cn
1313next-page : upper-type-bounds
1414previous-page : generic-classes
1515---
16+
17+ 型变是复杂类型的子类型关系与其组件类型的子类型关系的相关性。 Scala支持 [ 泛型类] ( generic-classes.html ) 的类型参数的型变注释,允许它们是协变的,逆变的,或在没有使用注释的情况下是不变的。 在类型系统中使用型变允许我们在复杂类型之间建立直观的连接,而缺乏型变则会限制类抽象的重用性。
18+
19+ ``` tut
20+ class Foo[+A] // A covariant class
21+ class Bar[-A] // A contravariant class
22+ class Baz[A] // An invariant class
23+ ```
24+
25+ ### 协变
26+
27+ 使用注释 ` +A ` ,可以使一个泛型类的类型参数 ` A ` 成为协变。 对于某些类 ` class List[+A] ` ,使 ` A ` 成为协变意味着对于两种类型 ` A ` 和 ` B ` ,如果 ` A ` 是 ` B ` 的子类型,那么 ` List[A] ` 就是 ` List[B] ` 的子类型。 这允许我们使用泛型来创建非常有用和直观的子类型关系。
28+
29+ 考虑以下简单的类结构:
30+
31+ ``` tut
32+ abstract class Animal {
33+ def name: String
34+ }
35+ case class Cat(name: String) extends Animal
36+ case class Dog(name: String) extends Animal
37+ ```
38+
39+ 类型 ` Cat ` 和 ` Dog ` 都是 ` Animal ` 的子类型。 Scala 标准库有一个通用的不可变的类 ` sealed abstract class List[+A] ` ,其中类型参数 ` A ` 是协变的。 这意味着 ` List[Cat] ` 是 ` List[Animal] ` ,` List[Dog] ` 也是 ` List[Animal] ` 。 直观地说,猫的列表和狗的列表都是动物的列表是合理的,你应该能够用它们中的任何一个替换 ` List[Animal] ` 。
40+
41+ 在下例中,方法 ` printAnimalNames ` 将接受动物列表作为参数,并且逐行打印出它们的名称。 如果 ` List[A] ` 不是协变的,最后两个方法调用将不能编译,这将严重限制 ` printAnimalNames ` 方法的适用性。
42+
43+ ``` tut
44+ object CovarianceTest extends App {
45+ def printAnimalNames(animals: List[Animal]): Unit = {
46+ animals.foreach { animal =>
47+ println(animal.name)
48+ }
49+ }
50+
51+ val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom"))
52+ val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex"))
53+
54+ printAnimalNames(cats)
55+ // Whiskers
56+ // Tom
57+
58+ printAnimalNames(dogs)
59+ // Fido
60+ // Rex
61+ }
62+ ```
63+
64+ ### 逆变
65+
66+ 通过使用注释 ` -A ` ,可以使一个泛型类的类型参数 ` A ` 成为逆变。 与协变类似,这会在类及其类型参数之间创建一个子类型关系,但其作用与协变完全相反。 也就是说,对于某个类 ` class Writer[-A] ` ,使 ` A ` 逆变意味着对于两种类型 ` A ` 和 ` B ` ,如果 ` A ` 是 ` B ` 的子类型,那么 ` Writer[B] ` 是 ` Writer[A] ` 的子类型。
67+
68+ 考虑在下例中使用上面定义的类 ` Cat ` ,` Dog ` 和 ` Animal ` :
69+
70+ ``` tut
71+ abstract class Printer[-A] {
72+ def print(value: A): Unit
73+ }
74+ ```
75+
76+ 这里 ` Printer[A] ` 是一个简单的类,用来打印出某种类型的 ` A ` 。 让我们定义一些特定的子类:
77+
78+ ``` tut
79+ class AnimalPrinter extends Printer[Animal] {
80+ def print(animal: Animal): Unit =
81+ println("The animal's name is: " + animal.name)
82+ }
83+
84+ class CatPrinter extends Printer[Cat] {
85+ def print(cat: Cat): Unit =
86+ println("The cat's name is: " + cat.name)
87+ }
88+ ```
89+
90+ 如果 ` Printer[Cat] ` 知道如何在控制台打印出任意 ` Cat ` ,并且 ` Printer[Animal] ` 知道如何在控制台打印出任意 ` Animal ` ,那么 ` Printer[Animal] ` 也应该知道如何打印出 ` Cat ` 就是合理的。 反向关系不适用,因为 ` Printer[Cat] ` 并不知道如何在控制台打印出任意 ` Animal ` 。 因此,如果我们愿意,我们应该能够用 ` Printer[Animal] ` 替换 ` Printer[Cat] ` ,而使 ` Printer[A] ` 逆变允许我们做到这一点。
91+
92+ ``` tut
93+ object ContravarianceTest extends App {
94+ val myCat: Cat = Cat("Boots")
95+
96+ def printMyCat(printer: Printer[Cat]): Unit = {
97+ printer.print(myCat)
98+ }
99+
100+ val catPrinter: Printer[Cat] = new CatPrinter
101+ val animalPrinter: Printer[Animal] = new AnimalPrinter
102+
103+ printMyCat(catPrinter)
104+ printMyCat(animalPrinter)
105+ }
106+ ```
107+
108+ 这个程序的输出如下:
109+
110+ ```
111+ The cat's name is: Boots
112+ The animal's name is: Boots
113+ ```
114+
115+ ### 不变
116+
117+ 默认情况下,Scala中的泛型类是不变的。 这意味着它们既不是协变的也不是逆变的。 在下例中,类 ` Container ` 是不变的。 ` Container[Cat] ` _ 不是_ ` Container[Animal] ` ,反之亦然。
118+
119+ ``` tut
120+ class Container[A](value: A) {
121+ private var _value: A = value
122+ def getValue: A = _value
123+ def setValue(value: A): Unit = {
124+ _value = value
125+ }
126+ }
127+ ```
128+
129+ 可能看起来一个 ` Container[Cat] ` 自然也应该是一个 ` Container[Animal] ` ,但允许一个可变的泛型类成为协变并不安全。 在这个例子中,` Container ` 是不变的非常重要。 假设 ` Container ` 实际上是协变的,下面的情况可能会发生:
130+
131+ ```
132+ val catContainer: Container[Cat] = new Container(Cat("Felix"))
133+ val animalContainer: Container[Animal] = catContainer
134+ animalContainer.setValue(Dog("Spot"))
135+ val cat: Cat = catContainer.getValue // 糟糕,我们最终会将一只狗作为值分配给一只猫
136+ ```
137+
138+ 幸运的是,编译器在此之前就会阻止我们。
139+
140+ ### 其他例子
141+
142+ 另一个可以帮助理解型变的例子是 Scala 标准库中的 ` trait Function1[-T, +R] ` 。 ` Function1 ` 表示具有一个参数的函数,其中第一个类型参数 ` T ` 表示参数类型,第二个类型参数 ` R ` 表示返回类型。 ` Function1 ` 在其参数类型上是逆变的,并且在其返回类型上是协变的。 对于这个例子,我们将使用文字符号 ` A => B ` 来表示 ` Function1[A, B] ` 。
143+
144+ 假设前面使用过的类似 ` Cat ` ,` Dog ` ,` Animal ` 的继承关系,加上以下内容:
145+
146+ ``` tut
147+ abstract class SmallAnimal extends Animal
148+ case class Mouse(name: String) extends SmallAnimal
149+ ```
150+
151+ 假设我们正在处理接受动物类型的函数,并返回他们的食物类型。 如果我们想要一个 ` Cat => SmallAnimal ` (因为猫吃小动物),但是给它一个 ` Animal => Mouse ` ,我们的程序仍然可以工作。 直观地看,一个 ` Animal => Mouse ` 的函数仍然会接受一个 ` Cat ` 作为参数,因为 ` Cat ` 即是一个 ` Animal ` ,并且这个函数返回一个 ` Mouse ` ,也是一个 ` SmallAnimal ` 。 既然我们可以安全地,隐式地用前者代替后者,我们可以说 ` Animal => Mouse ` 是 ` Cat => SmallAnimal ` 的子类型。
152+
153+ ### 与其他语言的比较
154+
155+ 某些与 Scala 类似的语言以不同的方式支持型变。 例如,Scala 中的型变注释与 C# 中的非常相似,在定义类抽象时添加型变注释(声明点型变)。 但是在Java中,当类抽象被使用时(使用点型变),才会给出型变注释。
0 commit comments