如何检查函数元素的协变/逆变位置?Scala协变报错解惑
1. 如何检查函数中元素的协变与逆变位置?
给你个接地气的判断思路,结合协变逆变的核心逻辑来梳理:
- 先明确基础规则:协变(用
+A标记)是说如果Dog是Animal的子类型,那Pets[Dog]也能当作Pets[Animal]使用;逆变(-A)则反过来,Pets[Animal]能当作Pets[Dog]使用。 - 协变位置:就是「往外拿东西」的位置——比如方法的返回值类型,或者不可变集合的元素(你只能读,不能往里写)。在这些位置放协变类型参数是安全的,因为拿出来的子类型对象完全可以被当作父类型使用。
- 逆变位置:就是「往里塞东西」的位置——比如方法的参数类型,或者函数的输入参数。这里放逆变类型参数才安全,因为你塞进去的子类型对象,能被当作父类型来处理。
- 快速校验小技巧:假设你的类型是协变的,要是某个位置的
A会让你在转型后能塞进不符合类型的东西,那这个位置就是逆变位置,协变类型绝对不能放这;反过来,逆变类型要是出现在输出位置,也会触发类型安全问题。
2. 解释Scala编译器的错误:"covariant type A occurs in contravariant position in type A of value pet2"
你大概率是写了类似这样的代码对吧?
class Pets[+A] { def add(pet2: A): Unit = { /* 这里是你的实现逻辑 */ } }
咱们拆解下编译器报错的原因:
- 你标记了
Pets[+A]是协变的,这意味着Pets[Dog]可以被向上转型成Pets[Animal](毕竟Dog是Animal的子类型)。 - 但
add方法的参数pet2: A属于「往里塞东西」的逆变位置。如果允许协变类型A出现在这里,会出现致命的类型安全问题:
举个具体的例子:
表面上val dogPets: Pets[Dog] = new Pets[Dog]() val animalPets: Pets[Animal] = dogPets // 协变规则允许这个转型 animalPets.add(new Cat()) // 这就出问题了!animalPets是Pets[Animal],理论上能加任何Animal子类,但它本质上是Pets[Dog],塞个Cat进去完全不符合类型,这就违反了Scala的类型安全规则。 - 所以编译器直接报错,禁止你把协变类型参数放到逆变位置。如果确实需要
add方法,你有两个常见解决方案:- 把协变改成不变:去掉
+,写成class Pets[A],这样就不允许跨类型转型了,自然不会有安全问题; - 用下界约束调整方法:把
add改成def add[B >: A](pet2: B): Pets[B],这样添加后返回的是更宽泛的Pets[B],既保留了类型安全,又能实现添加逻辑。
- 把协变改成不变:去掉
内容的提问来源于stack exchange,提问作者Chaitanya
相关产品推荐
相关产品推荐

