Scala协变技术疑问:val可行var不可行及Sequence方法问题
关于Scala协变的两个常见问题解答
一、为什么协变类中val可行但var不行?
首先得明确Scala协变的核心规则:标注为+T的协变类型参数,只能出现在「协变位置」(简单说就是只读、输出的场景),不能出现在「逆变位置」(可写、输入的场景)。
咱们用具体例子来说:
假设Dog是Animal的子类,那么协变的GenericCellImm[Dog]会被视为GenericCellImm[Animal]的子类——这符合协变的语义:"更具体的容器可以当成更宽泛的容器用"。
- 对于
val x: T:它是只读的,T只作为返回值(你获取x的时候得到的是T类型),属于协变位置,编译器完全允许,因为不会有类型安全问题。你把GenericCellImm[Dog]当成GenericCellImm[Animal],获取到的x还是Dog,这没问题,Dog本来就是Animal的子类。 - 对于
var x: T:var是可读写的,写操作(赋值)会让T出现在逆变位置。试想一下:如果允许GenericCellMut[+T]用var,那你可以把GenericCellMut[Dog]赋值给GenericCellMut[Animal]类型的变量,然后给这个变量的x赋值一个Cat(因为Cat也是Animal)——这就把Cat塞进了原本应该存Dog的容器里,完全破坏了类型安全!所以编译器直接禁止这种写法。
修复方案
如果需要可变且保持协变,有两种常见思路:
- 用私有var+只读getter,同时提供返回更宽泛类型的更新方法(利用下界):
class GenericCellMut[+T](private var _x: T) { // 只读的getter,T在协变位置 def x: T = _x // 更新方法:参数是T的父类U,返回GenericCellMut[U] def update[U >: T](newX: U): GenericCellMut[U] = { _x = newX.asInstanceOf[T] // 这里因为U是T的父类,强制转换安全 this.asInstanceOf[GenericCellMut[U]] } }
或者更纯粹的不可变更新(返回新实例):
class GenericCellMut[+T](val x: T) { def update[U >: T](newX: U): GenericCellMut[U] = new GenericCellMut(newX) }
- 如果不需要协变语义,直接去掉
+T,用不变的类型参数T,这样var就可以正常使用了。
二、Sequence[+A]的append方法存在什么问题?
这个问题和上面的var本质一样:协变类型参数A出现在了逆变位置。
看方法定义:def append(x: Sequence[A]): Sequence[A],这里的参数Sequence[A]是输入位置,属于逆变位置。还是用Animal/Dog的例子:
Sequence[Dog]是Sequence[Animal]的子类(因为协变)- 如果把
Sequence[Dog]当成Sequence[Animal]来调用append,方法会接受Sequence[Animal]类型的参数——比如你传入一个Sequence[Cat],这就会把Cat加到原本应该只包含Dog的序列里,类型安全直接被打破。
修复方案
给append方法添加一个类型参数B,并指定B是A的父类(下界),同时返回Sequence[B]:
abstract class Sequence[+A] { def append[B >: A](x: Sequence[B]): Sequence[B] }
这样一来:
- 当你调用
Sequence[Dog]的append时,参数可以是Sequence[B](B可以是Dog、Animal等) - 返回的是
Sequence[B],保证了序列里的元素类型都是B的子类,不会混入不兼容的类型,完美保持了协变的语义和类型安全。
内容的提问来源于stack exchange,提问作者alqueen
相关产品推荐
相关产品推荐

