Scala之旅——类型下界

类型上界限制了一个类型是另外一个类型的子类型,而类型下界则声明了一个类型是另外一个类型的父类型。B >: A表明了类型参数B或者抽象类型B是类型A的超类。在大多数场景中,A是作为类的类型参数,而B会作为方法的类型参数。

这里有个十分有用的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
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方法中的参数elemB类型的,且声明了为协变的。这之所以行不通,是在于函数在它们的参数类型上是逆变的,而在它们的返回类型上是协变的。

为了解决这个问题,我们需要转变prepend方法中参数类型的型变。我们可以引用新的类型U,它的类型下界是B,从而实现这一转变。

1
2
3
4
5
6
7
8
9
10
11
12
13
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)
}

现在我们便可以像下面这样操作了:

1
2
3
4
5
6
7
8
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

小鹏 wechat
公众号:数据Man
0%