上边界
在 Scala 中,类型参数和抽象类型可能受到类型界限的约束。这样的类型边界限制了类型变量的具体值并可能揭示此类类型成员的更多信息。上界 T <: A
声明变量类型 T
是 A
类型的子类型。 这里有个例子演示了 PetContainer
类型参数的上边界。
abstract class Animal {
def name: String
}
abstract class Pet extends Animal {}
class Cat extends Pet {
override def name: String = "Cat"
}
class Dog extends Pet {
override def name: String = "Dog"
}
class Lion extends Animal {
override def name: String = "Lion"
}
class PetContainer[P <: Pet](p: P) {
def pet: P = p
}
val dogContainer = new PetContainer[Dog](new Dog)
val catContainer = new PetContainer[Cat](new Cat)
// val lionContainer = new PetContainer[Lion](new Lion)
// ^this would not compile
类 PetContainer
接受的参数类型 P
必须要是 Pet
的子类型。因为 Dog
和 Cat
是 Pet
的子类型,所以我们可以创建一个新的 PetContainer[Dog]
和 PetContainer[Cat]
。但是,如果我们尝试创建一个 PetContainer[Lion]
,我们将会获得以下的错误:type arguments [Lion] do not conform to class PetContainer's type parameter bounds [P <: Pet]
(类型参数[Lion]
不符合 PetContainer's
的类型参数边界 [P <: Pet]
下边界
当上边界(upper type bounds)限制一个类型只能是另一个类型的子类型时,下边界(lower type bounds)声明一个类型只能是另一个类型的父类型。术语 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
是一个包含 B
类型元素(head
)和对列表 (tail
)其余部分引用的节点。因为我们有 +B
,所以 class Node
和它的子类型是协变的。
但是,这个程序没有通过编译,因为在 prepend
中的参数 elem
的类型 B
在声明中是协变的。这是不可以的,因为函数中的参数类型是逆变的并且返回类型是协变的。
为了解决这个问题,我们需要在 prepend
中转换参数 elem
为协变类型。接下来我们介绍新的类型参数 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
。