类型下界
类型上界限制了一个类型是另外一个类型的子类型,而类型下界则声明了一个类型是另外一个类型的父类型。 B >: A 表明了类型参数 B 或者抽象类型 B 是类型 A 的超类。在大多数场景中, A 是作为类的类型参数,而 B 会作为方法的类型参数。
这里有个十分有用的例子:
trait Node[+B] {
def prepend(elem: B): Node[B]
}
case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
def prepend(elem: B): ListNode[B] = ListNode(elem, this)
def head: B = h
def tail: Node[B] = t
}
case class Nil[+B]() extends Node[B] {
def prepend(elem: B): ListNode[B] = ListNode(elem, this)
}
这个程序实现了一个单向链表。 Nil 表示一个空的元素(比如一个空的列表)。 class ListNode 是一个 Node ,它包含了一个类型 B 的元素( head )和一个列表中其余元素的引用( tail )。 class Node 和它的子类型是协变的,因为这里有 +B 。
然而,这个程序不能通过编译,因为 prepend 方法中的参数 elem 是 B 类型的,且声明了为协变的。这之所以行不通,是在于函数在它们的参数类型上是逆变的,而在它们的返回类型上是协变的。
为了解决这个问题,我们需要转变 prepend 方法中参数类型的型变。我们可以引用新的类型 U ,它的类型下界是 B ,从而实现这一转变。
trait Node[+B] {
def prepend[U >: B](elem: U): Node[U]
}
case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
def head: B = h
def tail: Node[B] = t
}
case class Nil[+B]() extends Node[B] {
def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
}
现在我们便可以像下面这样操作了:
trait Bird
case class AfricanSwallow() extends Bird
case class EuropeanSwallow() extends Bird
val africanSwallowList= ListNode[AfricanSwallow](AfricanSwallow(), Nil())
val birdList: Node[Bird] = africanSwallowList
birdList.prepend(new EuropeanSwallow)
Node[Bird] 被分配到 africanSwallowList ,但是仍然可以接受 EuropeanSwallow 。