Scala类型参数

要点

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

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

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

  • 你能够用类型约束来约束一个方法,好比(implicit ev:T<:<UpperBound)code

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

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

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

泛型类

JavaC++一致,类和特质能够带类型参数。在Scala中,咱们用方括号来定义类型参数。编译器

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

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

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

你也能够本身指定类型:it

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]的超类。这种状况下,须要将类型参数声明为逆变的。

相关文章
相关标签/搜索