以下是this教程中的一些代码:
case class ListNode[+T](h: T, t: ListNode[T]) {
def head: T = h
def tail: ListNode[T] = t
def prepend(elem: T): ListNode[T] =
ListNode(elem, this)
}
教程说:
不幸的是,这个程序没有编译,因为协方差 只有在仅使用类型变量时才可以使用注释 协变立场。由于类型变量T显示为参数类型 方法前置,这个规则被打破了。
T
中predend
如何处于协变位置,而其他T
引用(def head: T = h
和def tail: ListNode[T] = t
)显然是协变的?
我问的是为什么T
中的prepend
不是协变的。这肯定不在Why is Function[-A1,...,+B] not about allowing any supertypes as parameters?中,这似乎是其他人指导我阅读的内容。
答案 0 :(得分:5)
方法的输入参数不是协变位置而是逆变位置。只有返回类型的方法处于协变位置。
如果您对ListNode
类的定义是确定,那么我可以编写如下代码:
val list1: ListNode[String] = ListNode("abc", null)
val list2: ListNode[Any] = list1 // list2 and list1 are the same thing
val list3: ListNode[Int] = list2.prepend(1) // def prepend(elem: T): ListNode[T]
val x: Int = list3.tail.head // ERROR, x in fact is "abc"
请参阅,如果参数是协变位置,那么容器总是可以保存另一种类型的值,该类型具有与其真实类型相同的祖先,这肯定是错误 !
因此,参考scala.collection.immutable.List.::的源代码,您的类应该定义为:
case class ListNode[+T](h: T, t: ListNode[T]) {
def head: T = h
def tail: ListNode[T] = t
def prepend[A >: T](elem: A): ListNode[A] = ListNode(elem, this)
}
参数类型A
是一个新的类型参数,其下限为T
。
答案 1 :(得分:1)
一个例子:
val dogList = ListNode[Dog](...)
val animal = Animal()
val dog = Dog()
dogList.prepend(dog) //works
dogList.prepend(animal) //fails
协方差意味着您可以像ListNode [Animal]一样使用ListNode [Dog]
但:
//if compiler would allow it
val animalList: ListNode[Animal] = dogList
animalList.prepend(dog) //works
animalList.prepend(animal) //would fail, but you expect an animallist to accept any animal