Scala类型参数

要点

  • 类、特质、方法和函数都可以有类型参数。

  • 将类型参数放置在名称之后,以方括号括起来。

  • 类型界定的语法为T<: UpperBoundT>: LowerBoundT<% ViewBound
    T: ContextBound

  • 你可以用类型约束来约束一个方法,比如(implicit ev:T<:<UpperBound)

  • +T(协变)来表示某个泛型类的子类型关系和参数T方向一致,或用-T(逆变)来表示方向相反。

  • 协变适用于表示输出的类型参数,比如不可变集合中的元素。

  • 逆变适用于表示输入的类型参数,比如函数参数。

泛型类

JavaC++一致,类和特质可以带类型参数。在Scala中,我们用方括号来定义类型参数。

class Pair[T, S](val first: T, val second: S)

Scala会从构造参数中推断出实际类型:

val p = new Pair(42, "String")

你也可以自己指定类型:

val p = new Pair[Any, Any](42, "String")

泛型函数

函数和方法也可以带有类型参数:

def getMiddle[T](a: Array[T]) = a(a.length / 2)

Scala会从调用该方法使用的实际类型来推断出类型:

val middle = getMiddle(Array("a", "b", "c", "d", "e"))
println(middle) //c

类型变量界定

有时候你需要对类型变量进行限制。考虑这样一个Pair类型,它要求它的两个组件类型相同:

class Pair[T](val first: T, val second: T)

现在希望添加一个方法,产出较小的那个值:

class Pair[T](val first: T, val second: T) {
    def smaller = if (first.compareTo(second) < 0) first else second
  }

这是错的,因为我们并不知道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加了一条限制:T必须是Comparable[T]的子类型。原来给T指定什么类型都可以,现在就不行了。
你也可以为类型指定一个下界。举例来说,假定我们想要定义一个方法,用另一个值替换对偶的第一个组件。我们的对偶是不可变的,因此我们需要返回一个新的对偶。

class Person

  class Student extends Person

  class Pair[T](val first: T, val second: T) {
    def replaceFirst(newFirst: T) = new Pair[T](newFirst, second)
  }

假定我们有一个Pair[Student],我们应该允许用一个Person来替换第一个组件,实际上这样是不可行的,因此,通过在函数后面定义下界来实现。

class Pair[T](val first: T, val second: T) {
    def replaceFirst[R >: T](newFirst: R) = new Pair[R](newFirst, second)
  }

通常而言,替换进来的类型必须是原来类型的超类型。为了清晰,我给返回的对偶也写了类型参数,实际上不需要。

视图界定

在前一节,有一个带上界的示例:

class Pair[T <: Comparable[T]]

如果你试着new一个Pair(4,2),编译器会抱怨说Int不是Comparable的子类型,ScalaInt类型并没有实现Comparable。不过,RichInt实现了Comparable[Int],同时还有一个从IntRichInt的隐式转换。
解决办法是使用视图界定:

class Pair[T <% Comparable[T]](val first: T, val second: T) {
    def smaller = if (first.compareTo(second) < 0) first else second
  }

<% 关系意味着T可以被隐式转换成Comparable[Int]

个人理解:不管是类型变量界定还是视图界定,实际上都是在限制类型参数T,类型变量界定要求类型参数T必须是上界的子类或者是下界的父类;视图界定则是要求类型参数T必须能够隐式转换成“类似上界”的界定,比如上面提到的,Int隐式转换成RichIntRichIntComparable[Int]的子类。这样看来,类型变量界定对类型参数的限制比视图界定对类型参数的限制是更大了。

上下文界定

视图界定T<%V要求T必须能够隐式转换到V,上下文界定的形式为T:M,其中M是另一个泛型类。它要求必须存在一个类型为M[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上下文界定

Manifest were added specially to handle arrays
要实例化一个泛型的Array[T],我们需要一个Manifest[T]对象。要想让基本类型的数组能够正常工作的话,这是必须的。举例来说,如果TInt,你会希望虚拟机中对应的是一个int[]数组。在Scala中,Array只不过是类库提供的一个类,编译器并不对它做特殊处理。如果你要编写一个泛型函数来构造泛型数组的话,你需要传入这个Manifest对象来帮忙。由于它是构造器的隐式参数,你可以用上下文界定:

def makePair[T: Manifest](first: T, second: T) = {
    val r = new Array[T](2)
    r(0) = first
    r(1) = second
  }

如果你调用makePair(4,9),编译器将定位到隐式的Manifest[Int]并实际上调用makePair(4,9)(intManifest)。这样一来,该方法调用的就是new Array(2)(intManifet),返回基本类型的数组int[2]
为什么搞这么复杂?在虚拟机中,泛型相关的类型信息是被抹掉的。只会有一个makePair方法,却要处理所有的类型T

类型约束

类型约束提供的是另一个限定类型的方式。总共有三种关系可供使用:
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<:Comparable[T]]有更多的优点。不过在某些场景下,类型约束会很有用。

  • 类型约束让你可以在泛型类中定义只能在特定条件下使用的方法,示例如下:

class Pair[T](val first: T, val second: T) {
    def smaller(implicit ev: T <:< Comparable[T]) =
      if (first.compareTo(second) < 0) first else second
  }

  val p1 = new Pair("a", "b") //a

你可以构造出Pair[File],尽管File并不是带有先后次序的。只有当你调用smaller方法的时候才会报错。

型变

假定我们有一个函数对Pair[Person]做某种处理:

def makeFriends(p: Pair[Person])

如果StudentPerson的子类,我们用Pair[Student]作为参数调用makeFriends,这是个错误,因为虽然StudentPerson的子类,但是Pair[Student]Pair[Person]一点关系都没有。如果你想要这样的关系,则必须在定义Pair类的时候表明这一点:

class Pair[+T](val first: T, val second: T)

+号意味着如果StudentPerson的子类,那么Pair[Student]也是Pair[Person]的子类。
也可以有另一个方向的型变。考虑泛型类型Friend[T],表示希望与类型T的人成为朋友的人:

trait Friend[-T] {
    def befriend(someone: T)
  }

现在假定有一个函数:

def makeFriendWith(s: Student, f: Friend[Student]) {
    f.befriend(s)
  }

你能用Friend[Person]作为参数调用它吗?也就是说,如果你有:

class Person extends Friend[Person]
  class Student extends Person
  val susan = new Student
  val fred = new Person

函数调用makeFriendWith(susan,fred)能成功吗?看上去应该可以,因为fred想和任何人叫交朋友,他也一定会和susan交朋友。注意到这个时候,类型变化的方向和子类型方向是相反的。StudentPerson的子类,但是Friend[Student]Friend[Person]的超类。这种情况下,需要将类型参数声明为逆变的。

相关推荐