我在 Scala 食谱中发现了一个示例。我理解了该示例,并进行了一些更改以确保我彻底理解了该示例。
代码示例,
trait Animal {
def speak
}
class Dog(var name: String) extends Animal {
def speak { println("Dog says woof") }
}
class SuperDog(name: String) extends Dog(name) {
override def speak { println("I'm a SuperDog") }
}
// solution 1
class Container[+T](val elem : T)
def makeDogsSpeak(dogHouse: Container[Dog]) {
dogHouse.elem.speak
}
val superDogHouse = new Container(new SuperDog("Wonder Dog"))
makeDogsSpeak(superDogHouse)
// solution 2
class Container[T](val elem : T) // remove covariant from type
def makeDogsSpeak[U <: Dog](dogHouse: Container[U]) {
dogHouse.elem.speak
}
val superDogHouse = new Container(new SuperDog("Wonder Dog"))
makeDogsSpeak(superDogHouse)
- 解决方案 1 相对于解决方案 2 有什么优势吗?或者它是一个 偏好问题?
- 对于解决方案 1,我将代码修改为,
.
class Container[T](val elem : T) // remove covariant from type
val superDogHouse = new Container(new SuperDog("Wonder Dog"))
makeDogsSpeak(superDogHouse) // compilation error
但是如果我这样做,
makeDogsSpeak(new Container(new SuperDog("Wonder Dog"))) // superDogHouse removed
编译器编译。这里发生了什么?这不是“相同”的代码吗?谢谢
最佳答案
关于差异声明(问题 1):
声明方差异:
class Container[+T](val elem : T)
使用方差异:
def makeDogsSpeak(dogHouse: Container[_ <: Dog])
使用声明或使用方差异表示法取决于您的代码设计。在某些情况下,方法可以是泛型的(并且可以使用短符号的类型推断),但在某些情况下,方法不能是泛型的。
对于某些类,参数协方差只是良好设计的一种方法。例如:
trait List[+A]
object Nil extends List[Nothing]
使用:
val xs : List[Int] = Nil // will not possible in case List[A]
此外,在某些情况下,需要混合声明和使用侧面方差,因为容器中参数的位置与方差相关。
协变位置参数:
trait C[+T] {
def get : T
}
逆变位置:
trait C[-T] {
def set(x : T): Unit
}
但不是:
trait C[+T] {
def get : T
def set(x : T): Unit // wrong position of type T
}
通过混合声明和使用方差异来修复:
trait C[+T] {
def get : T
def set[TT >: T](x : TT): Unit // ok
}
关于问题 2:
考虑到类型推断的规则,对于编译器来说,您的代码如下所示:
val superDogHouse = new Container[SuperDog](new SuperDog("Wonder Dog"))
makeDogsSpeak(superDogHouse) // compilation error
和
makeDogsSpeak(new Container[Dog](new SuperDog("Wonder Dog")))
它在第一种情况下起作用而在第二种情况下不起作用的原因是容器的类型。
关于Scala 协变容器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24161491/