本章要点
- 类、特质、方法和函数都可以有类型参数
- 将类型参数放置在名称之后,以方括号括起来。
- 类型界定的语法为 T <: UpperBound、 T >: LowerBound、 T <% ViewBound、 T : ContextBound
- 你可以用类型约束来约束另一个方法,比如(implicit ev: T <:< UpperBound)
- 用+T(协变)来表示某个泛型类的子类型关系和参数T方向一致, 或用-T(逆变)来表示方向相反。
- 协变适用于表示输出的类型参数,比如不可变集合中的元素
- 逆变适用于表示输入的类型参数,比如函数参数
泛型类
和Java或C++一样,类和特质可以带类型参数。在Scala中,使用方括号来定义类型参数:
class Pair[T, S] (val first: T, val second: S)
带有一个或多个类型参数的类是泛型的。
泛型函数
def getMiddle[T](a: Array[T]) = a(a.length / 2)
类型变量界定
有时, 你需要对类型变量进行限制。
class Pair[T](val first: T, val second: T) {
def smaller = if (first.compareTo(second) < 0) first else second // error
}
这是错误的,我们并不知道first是否有compareTo方法。要解决这个问题,我们可以添加一个上界 T <: Comparable[T] :
class Pair[T <: Comparable[T]](val first: T, val second: T) {
def smaller = if (first.compareTo(second) < 0) first else second
}
这里T必须是Comparable[T]的子类型。这样我们就可以实例化Pair[java.lang.String],但是不能实例化Pair[java.io.File] 。
你可以为了类型指定一个下界。例如:
class Pair[T] (val first: T, val second: T) {
def peplaceFirst[newFirst: T] = new Pair[T] (newFirst, second)
}
假定我们有一个Pair[Student],我们应该允许使用一个Person来替换第一个组件。这样做的结果将会是一个Pair[Person]。通常而言,替换进来的类型必须是原类型的超类型。
def replaceFirst[R >: T] (newFirst: R) = new Pair[R](newFirst, second)
// 或者
def replaceFirst[R >: T] (newFirst: R) = new Pair(newFirst, second) // 类型推导
**注意: **如果不写下界:
def replaceFirst[R] (newFirst: R) = new Pair(newFirst, second)
该方法可以编译通过,但是它将返回Pair[Any].
视图界定
在面前上界时我们提供了一个示例:
class Pair[T <: Comparable[T]]
但是如果你new一个Pair(4,2), 编译器会抱怨说Int不是Comparable[Int]的子类型。因为Scala的Int类型没有实现Comparable,不过RichInt实现了Comparable[Int],同时还有一个从Int到RichInt的隐式转换。
解决方法是使用“视图界定”:
class Pair[T <% Comparable[T]]
<% 意味着T可以被隐式转换成Comparable[T]。
**说明: **用Ordered特质会更好,它在Comparable的基础上额外提供了关系操作符:
class Pair[T <% Ordered[T]] (val first: T, val second: T) {
def smaller = if( first < second ) first else second
}
上下文界定
视图界定 T <% V 要求必须存在一个从T到V的隐式转换,上下文界定的形式为 T:M,其中M是另一个泛型类。它要求必须存在一个类型为M[T]的“隐式值”。例如:
class Pair[T: Ordering]
上述定义要求必须存在一个类型为Ordering[T]的隐式值。当你声明一个使用隐式值的方法时,你需要添加一个“隐式参数”。例如:
class Pair[T: Ordering] (val first: T, val second: T) {
def smaller(implicit ord: Ordering[T]) = {
if( ord.compare(first, second) < 0) first else second
}
}
Manifest上下文界定
要实例化一个泛型的Array[T],我们需要一个Manifest[T]对象。 如果你要编写一个泛型函数来构造泛型数组的话,你需要传入这个Manifest对象来帮忙。由于它是构造器的隐式参数,可以用上下文界定:
def makePair[T: Manifest](first: T, second: T) {
val r = new Array[T](2)
r(0) = first
r(1) = second
r
}
多重界定
类型变量可以同时有上界和下界。写法为:
T >: Lower <: Upper
你不能同时有多个上界或多个下界。不过你可以要求一个类型实现多个特质:
T <: Comparable[T] with Serializable with Cloneable
你也可以有多个视图界定:
T <% Comparable[T] <% String
你也可以有多个上下文界定:
T : Ordering : Manifest
类型约束
类型约束提供给你的是另一个限定类型的方式。总共有三种关系可供使用:
T =:= U // T是否等于U
T <:< U // T是否为U的子类型
T <%< U // T能否被视图(隐士)转换为U
要使用这样一个约束,你需要添加“隐式类型证明参数”:
class Pair[T](val first: T, val second: T) (implicit ev: T <:< Comparable[T])
类型约束让你可以在泛型类中定义只能在特定条件下使用的方法。例如:
class Pair[T] (val first: T, val second: T) {
def smaller(implicit ev: T <:< Ordered[T]) = {
if (first < second) first else second
}
}
在这里你可以构造出Pair[File], 尽管File并不是带有先后次序的。只有当你调用smaller方法时,才会报错。
另一个示例是Option类的orNull方法:
val friends = Map("Fred" -> "Barney", ...)
val friendOpt = friends.get("Wilma")
val friendOrNull = friendOpt.orNull // 要么是String,要么是null
这种做法并不适用于值类型,例如Int。因为orNUll实现带有约束Null <:< A, 你仍然可以实例化Option[Int],只要你别使用orNull就好了。
类型约束的另一个用途是改进类型推断。例如:
def firstLast[A, C <: Iterable[A]](it: C) = (it.head, it.last)
当你执行如下代码:
firstLast(List(1,2,3))
你会得到一个消息,推断出的类型参数[Nothing, List[Int]]不符合[A, C <: Iterable[A]]。类型推断器单凭List(1,2,3)无法判断出A是什么,因为它在同一个步骤中匹配到A和C。解决的方法是首先匹配C,然后在匹配A:
def firstLast[A, C] (it: C) (implicit ev: C <:< Iterable[A]) = (it.head, it.last)
型变
假定我们有一个函数对pair[Person]做某种处理:
def makeFriends(p: Pair[Person])
如果Student是Person的子类,那么可以用Pair[Student]作为参数调用吗?缺省情况下,这是个错误。尽管Student是Person的子类型,但Pair[Student]和Pair[Person]之间没有任何关系。
如果你想要这样的关系,则必须在定义Pair类时表明这一点:
class Pair[+T] (val first: T, val second: T)
+号意味着该类型是与T协变的-----也就是说,它与T按痛样的方向型变。由于Student是Person的子类,Pair[Student]也就是Pair[Person]的子类型。
也可以有另一个方向的型变--逆变。例如:泛型Friend[T],表示希望与类型T的人成为朋友的人。
trait Friend[-T] {
def befriend(someone: T)
}
// 有这么一个函数
def makeFriendWith(s: Student, f: Friend[Student]) { f.befriend(s) }
class Person extends Friend[Person]
class Student extends Person
val s = new Student
val p = new Person
函数调用makeFriendWith(s, p)能成功吗?这是可以的。
这里类型变化 的方向和子类型方向是相反的。Student是Person的子类,但是Friend[Student]是Friend[Person]的超类。这就是逆变。
在一个泛型的类型声明中,你可以同时使用这两中型变,例如 单参数函数的类型为Function1[-A, +R]
def friends(students: Array[Atudent], find: Function1[Student, Person]) = {
for (s <- students) yield find(s)
}
协变和逆变点
从上面可以看出函数在参数上是逆变的,在返回值上则是协变的。通常而言,对于某个对象消费的值适用逆变,而对于产出它的值则适用于协变。 如果一个对象同时是消费和产出值,则类型应该保持不变,这通常适用于可变数据结构。
如果试着声明一个协变的可变对偶,则是错误的,例如:
class Pair[+T] (var first: T, var second: T) // 错误
不过有时这也会妨碍我们做一些本来没有风险的事情。例如:
class Pair[+T] (var first: T, var second: T) {
def replaceFirst(newFirst: T) = new Pair[T](newFirst, second) // error T出现在了逆变点
}
// 解决方法是给方法加上另一个类型参数:
class Pair[+T] (var first: T, var second: T) {
def replaceFirst[R >: T](newFirst: R) = new Pair[R](newFirst, second)
}
对象不能泛型
你不能给对象添加类型参数。
类型通配符
在Java中,所有泛型类都是不变的。不过,你可以在使用通配符改变它们的类型。例如:
void makeFriends(Pair<? extends Person> people) // Java代码
可以用List<Student>作为参数调用。
你也可以在Scala中使用通配符
def process(people: java.util.List[_ <: Person]) // scala
在Scala中,对于协变的Pair类,不需要通配符。但是假定Pair是不变的:
class Pair[T] (var first: T, var second: T)
// 可以定义函数:
def makeFriends (p: Pair[_ <: Person]) // 可以用Pair[Student]调用
逆变使用通配符:
import java.util.Comparator
def min[T](p: Pair[T]) (comp: Comparator[_ >: T])