上边界
在 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。

本文深入探讨Scala中的类型边界概念,包括上边界和下边界,并通过具体示例展示如何使用这些边界来约束类型参数,确保代码的灵活性和安全性。
296

被折叠的 条评论
为什么被折叠?



