快学Scala第17章----类型参数

本章要点

  • 类、特质、方法和函数都可以有类型参数
  • 将类型参数放置在名称之后,以方括号括起来。
  • 类型界定的语法为 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 people)   // Java代码

可以用List作为参数调用。

你也可以在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])

你可能感兴趣的:(快学Scala第17章----类型参数)