泛型类
class GenericClass[S,T](val s:S, val t:T) {
}
// 实例化方式1
val generic = new GenericClass[String, Int]("s", 1)
// 实例化方式2:类型推导
val generic = new GenericClass("s", 1)
泛型方法
class GenericClass[S,T](val s:S, val t:T) {
//参数T与类定义的T无关
def getData[T](list:List[T]) = list(list.length/2)
}
val generic = new GenericClass("s", 1)
//方式1
println(generic.getData[Int](List(1,2,3))) //2
//方式2:类型推导
println(generic.getData(List(1,2,3))) //2
//方式3:获得getData的引用,必须指定泛型类型
val f=generic.getData[Int]_
println(f(List(1,2,3))) //2
类型变量界定(Type Variable Bound)
// 编译错误
def typeVariable[T](a: T, b: T) = {
if (a.compareTo(b) > 0) 1
else -1
}
// 通过编译,为范型T增加了约束,限定上界为Comparable[T]
// 范型类型T也就具有了Comparable[T]中的compareTo(T)方法,类似于java中的继承
def typeVariable[T <: Comparable[T]](a: T, b: T) = {
if (a.compareTo(b) > 0) 1
else -1
}
// 正确运行,String有上界Comparable[T]
println(typeVariable("scala","java"))
// 运行错误,Int没有上界Comparable[T],但是为何编译不报错?
// 如果想成功运行的话,就需要进行一次隐式转换,将Int类型转换成支持Comparable[T]的类型,此时应该改用视图界定<%
// 视图界定帮我们进行了隐式转换,将Int转换成了支持Comparable[T]的RichInt类型
println(typeVariable(1,2))
视图界定(View Bound)
// <% 泛型视图界定符,表示把传入不是Comparable[T]类型的 隐式传换 为Comparable[T]类型
class PairNotPerfect[T <% Comparable[T]](val first: T, val second: T) {
// first之所以能使用方法compareTo,是因为first 被隐式传换 为Comparable[T]类型
def bigger = if (first.compareTo(second) > 0) first else second
}
val pair = new PairNotPerfect(1, 2)
println(pair.bigger)
上下文界定(Context Bound)
上下文界定是隐式参数的语法糖。类型参数形式为T:M的形式,其中M是一个泛型类,这种形式要求存在一个M[T]类型的隐式值
class Person(val age: Int) {
println("person==> " + age)
}
// PersonOrdering继承了Ordering[T],而Ordering[T]又继承了Comporator[T],所以下面方法中有compare(x: T, y: T)方法
class PersonOrdering extends Ordering[Person] {
override def compare(x: Person, y: Person): Int = {
if (x.age > y.age) 1 else -1
}
}
// 该类定义了一个上下文界定,意思是在其作用域内,必须有一个Ordering[T]的隐式值,而这个隐式值可以作用于内部的方法
class Pair[T: Ordering](val first: T, val second: T) {
// 该方法需要一个类型为Ordering[T]的隐式参数
def old(implicit ord: Ordering[T]) = {
if (ord.compare(first, second) > 0) first else second
}
}
// 定义一个隐式值,类型为Ordering[T]
implicit val po = new PersonOrdering
val p = new Pair(new Person(18), new Person(19))
// 调用old方法时,不需要传入参数,根据我们的上下文界定要求,po满足要求,因此作为参数传入old
println(p.old.age) // 19
Manifest关键字
Manifest是scala2.8引入的一个特质,用于编译器在运行时也能获取泛型类型的信息。
在JVM上,泛型参数类型T在运行时是被“擦拭”掉的,编译器把T当作Object来对待,所以T的具体信息是无法得到的;为了使得在运行时得到T的信息,scala需要额外通过Manifest来存储T的信息,并作为参数用在方法的运行时上下文。
def test[T](x: T, m: Manifest[T]) {
}
上述的方法要求调用者要额外传入m参数,非常不友好,好在scala中有隐式转换、隐式参数的功能,在这个地方可以用隐式参数来减轻调用者的麻烦
def foo[T](x: List[T])(implicit m: Manifest[T]) = {
//manifest定义在Predef中,直接使用即可
if (m <:< manifest[String])
println("Hey, this list is full of strings")
else
println("Non-stringy list")
}
foo(List("one", "two")) // Hey, this list is full of strings
foo(List(1, 2)) // Non-stringy list
foo(List("one", 2)) // Non-stringy list
不过上面的foo 方法定义使用隐式参数的方式,仍显得啰嗦,于是scala里又引入了“上下文界定“(通常Manifest会以隐式参数和上下文绑定的形式使用),可以简化为:
def foo[T : Manifest] (x : List[T])
这个机制起因是scala2.8对数组的重新设计而引入的,原本只是为了解决数组的问题,后续被用在更多方面
def arrayMake[T: Manifest](first: T, second: T) = {
//数组在声明时必须要求指定具体的类型,所以泛型要用Manifest
val r = new Array[T](2)
r(0) = first
r(1) = second
r
}
arrayMake(1,2).foreach(println) //1 2
scala在2.10里却用TypeTag替代了Manifest,用ClassTag替代了ClassManifest,原因是在路径依赖类型中,Manifest拿到的信息是不精确的
ClassTag关键字
替代ClassManifest
保存了泛型擦除后的原始类型T,运行时指定在编译的时候无法指定的类型信息
// 编译器会翻译成implicit,因为ClassTag中间有隐式参数和隐式值
def mkArray[T: ClassTag](elems:T*) = Array[T](elems:_*)
//mkArray: mkArray[T](val elems: T*)(implicit <synthetic> val evidence$2: scala.reflect.ClassTag[T]) => Array[T]
//这时候ClassTag就可以把Int类型这个信息传递给编译器
mkArray(10,20).foreach(println) //10 20
//res0: Array[Int] = [I@7b9cc588
多重界定
/*
// 表示:A和B为T上界
T <: A with B
// 表示:A和B为T下界
T >: A with B
// 表示:同时拥有上界和下界,并且A为下界,B为上界,A为B的子类,顺序不能颠倒。
T >: A <: B
// 表示:类型变量界定,即同时满足AT这种隐式值和BT这种隐式值
T:A:B
// 表示:视图界定,即同时能够满足隐式转换的A和隐式转换的B
T <% A <% B
*/
类型约束
// A =:= B // 表示A类型等同于B类型
// A <:< B // 表示A类型是B类型的子类
def rocky[T](i: T)(implicit ev: T <:< java.io.Serializable) {
println("...")
}
rocky("Spark")