泛型类
类和特质可以带类型参数,Scala中,我们用方括号来定义类型参数,例如:
class Pair[T, S](val first: T, val second: S)
以上将定义一个带有两个类型参数T和S的类。在类的定义中,你可以用类型参数来定义变量、方法参数、以及返回值的类型。
Scala会从构造参数推断出实际类型:
val p = new Pair(30, "aaaa") // 这是一个Pair[Int, String]
你也可以自己指定类型:
val p2 = new Pair[Any, Any](30, "aaaa")
泛型函数
函数和方法也可以带类型参数。以下是一个简单的示例:
def getMiddle[T](a: Array[T]) = a(a.length / 2)
和泛型类一样,你需要把类型参数放在方法名之后。Scala会从调用该方法使用的实际参数来推断出类型。
getMiddle(Array ("Mary", "had", "a", "little", "lamb")) // 将会调用getMiddle[String]
如有必要,你也可以指定类型:
val f = getMiddle[String] _ // 这是具体的函数,保存到f
类型变量界定
有时,你需要对类型变量进行限制。虑这样一个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必须是Comparable[T]的子类型。
你也可以为类型指定一个下界;举例来说,假定我们想要定义一个方法,用另一个值替换对偶的第一个组件,我们的对偶是不可变的,因此需要返回一个新的对偶,如下:
class Pair [T](val first: T, val second: T) {
def replaceFirst(newFirst: T) = new Pair[T](newFirst, second)
}
可以有更好的方法;通常而言,替换进来的类型必须是原类型的超类型。
def replaceFirst[R >: T](newFirst: R) = new Pair[R](newFirst, second)
// 也可以写成:
def replaceFirst[R >: T](newFirst: R) = new Pair(newFirst, second)
// 返回值会被正确推断为new Pair[R]
注意:如果你不写上界,def replaceFirst[R](newFirst: R) = new Pair(newFirst, second)
,可以通过编译,但它将返回Pair[Any]。
视图界定
class Pair [T <: Comparable[T]]
如果new一个Pair(4, 2),,编译器会抱怨说Int不是Comparable[Int]的子类型。和java.lang.Integer包装类型不同,Scala的Int类型并没有实现Comparable。不过,RichInt实现了Comparable[Int],同时还有一个从Int到RichInt的隐式转换。
解决方法是使用“视图界定(view bound)”,就像这样:
class Pair [T <% Comparable[T]]
<%
意味着T可以被隐式转换成Comparable[T]。不过,Scala的视图界定即将退出历史舞台,如果你在编译时打开-future
选项,使用视图界定将收到编译器的警告,你可以用“类型约束( type constraint )”替换视图界定,就像这样:
class Pair [T](val first: T, val second: T)(implicit ev: T => Comparable[T]) {
def smaller = if (first.compareTo(second) < 0) first else second
...
}
上下文界定
视图界定T <% V
要求必须存在一个从T到V的隐式转换。上下文界定(context bound)的形式为T
:M
,其中M是另一个泛型类。它要求必须存在一个类型为M[T]的“隐式值(implicit value)”。例如:
class Pair[T : Ordering]
上述定义要求必须存在一个类型为Ordering[T]的隐式值。该隐式值可以被用在该类的方法中,当你声明一个使用隐式值的方法时,需要添加一个 “隐式参数(implicit parameter)”。如下示例:
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
}
ClassTag上下文界定
要实例化一个泛型的Array[T],我们需要一个ClassTag[T]对象。要想让基本类型的数组能正常工作的话,这是必需的。举例来说,如果T是Int,你会希望虚拟机中对应的是一个int[]数组,如果你编写的是一个构造泛型数组的泛型函数,则你需要帮它一下 ,传给它那个类标签(class tag)对象,用上下文界定即可,就像这样:
import scala.reflect._
def makePair[T: ClassTag](first: T, second: T){
val r = new Array[T](2);
r(0) = first
r(1) = second
r
}
如果你调用makePair(4,9),编译器将定位到隐式的ClassTag[T]并实际上调用makePair(4,9)(classTag)。这样一来,该方法调用的就是classTag.newArray
,在本例中这是一个将构造出基本类型数组int[2]的ClassTag[Int]。在虚拟机中,泛型相关的类型信息是被抹掉的,这时只会有一个makePair方法,其却要处理所有类型T。
参考:快学scala(第二版)