类和特质都可以带类型参数,用方括号来定义类型参数,可以用类型参数来定义变量、方法参数和返回值。带有一个或多个类型参数的类是泛型的。如下p1,如果实例化时没有指定泛型类型,则scala会自动根据构造参数的类型自动推断泛型的具体类型。
class Pair[T, S](val first: T, val second: S) {
override def toString = "(" + first + "," + second + ")"
}
//从构造参数推断类型
val p1 = new Pair(42, "String")
//设置类型
val p2 = new Pair[Any, Any](42, "String")
函数或方法也可以有类型(泛型)参数。
// 从参数类型来推断类型
println(getMiddle(Array("Bob", "had", "a", "little", "brother")).getClass.getTypeName)
//指定类型,并保存为具体的函数。
val f = getMiddle[String] _
println(f(Array("Bob", "had", "a", "little", "brother")))
在Java泛型里不表示某个泛型是另外一个泛型的子类型可以使用extends关键字,而在scala中使用符号“<:”,这种形式称之为泛型的上界。
class Pair1[T <: Comparable[T]](val first: T, val second: T) {
def smaller = if (first.compareTo(second) < 0) first else second
}
object Main1 extends App{
override def main(args: Array[String]): Unit = {
val p = new Pair1("Fred", "Brooks")
println(p.smaller)
}
}
在Java泛型里表示某个泛型是另外一个泛型的父类型,使用super关键字,而在scala中,使用符号“>:”,这种形式称之为泛型的下界。
class Pair2[T](val first: T, val second: T) {
def replaceFirst[R >: T](newFirst: R) = new Pair2[R](newFirst, second)
override def toString = "(" + first + "," + second + ")"
}
object Main2 extends App{
override def main(args: Array[String]): Unit = {
val p = new Pair2("Nick", "Alice")
println(p)
println(p.replaceFirst("Joke"))
println(p)
}
}
在Java中,T同时是A和B的子类型,称之为多界,形式如:<T extends A & B>。
在Scala中,对上界和下界不能有多个,但是可以使用混合类型,如:[T <: A with B]。
在Java中,不支持下界的多界形式。如:<T super A &B>这是不支持的。
在Scala中,对复合类型依然可以使用下界,如:[T >: A with B]。
在Scala中,如果你想标记某一个泛型可以隐式的转换为另一个泛型,可以使用:[T <% Comparable[T]],由于Scala的Int类型没有实现Comparable接口,所以我们需要将Int类型隐式的转换为RichInt类型,比如:
class Pair3[T <% Comparable[T]](val first: T, val second: T) {
def smaller = if (first.compareTo(second) < 0) first else second
override def toString = "(" + first + "," + second + ")"
}
object Main3 extends App {
val p = new Pair3(4, 2)
println(p.smaller)
}
视图界定 T <% V要求必须存在一个从T到V的隐式转换。上下文界定的形式为T:M,其中M是另一个泛型类,它要求必须存在一个类型为M[T]的隐式值。
下面类定义要求必须存在一个类型为Ordering[T]的隐式值,当你使用了一个使用了隐式值得方法时,传入该隐式参数。
class Pair4[T: Ordering](val first: T, val second: T) {
def smaller(implicit ord: Ordering[T]) = {
println(ord)
if (ord.compare(first, second) < 0) first else second
}
override def toString = "(" + first + "," + second + ")"
}
object Main4 extends App{
override def main(args: Array[String]): Unit = {
val p4 = new Pair4(1, 2)
println(p4.smaller)
}
}
Manifest是scala2.8引入的一个特质,用于编译器在运行时也能获取泛型类型的信息。在JVM上,泛型参数类型T在运行时是被“擦拭”掉的,编译器把T当作Object来对待,所以T的具体信息是无法得到的;为了使得在运行时得到T的信息,scala需要额外通过Manifest来存储T的信息,并作为参数用在方法的运行时上下文。
def test[T] (x:T, m:Manifest[T]) { ... }
有了Manifest[T]这个记录T类型信息的参数m,在运行时就可以根据m来更准确的判断T了。但如果每个方法都这么写,让方法的调用者要额外传入m参数,非常不友好,且对方法的设计是一道伤疤。好在scala中有隐式转换、隐式参数的功能,在这个地方可以用隐式参数来减轻调用者的麻烦。
def foo[T](x: List[T])(implicit m: Manifest[T]) = {
println(m)
if (m <:< manifest[String])
println("Hey, this list is full of strings")
else
println("Non-stringy list")
}
foo(List("one", "two"))
foo(List(1, 2))
foo(List("one", 2))
隐式参数m是由编译器根据上下文自动传入的,比如上面是编译器根据 "one","two" 推断出 T 的类型是 String,从而隐式的传入了一个Manifest[String]类型的对象参数,使得运行时可以根据这个参数做更多的事情。
不过上面的foo 方法定义使用隐式参数的方式,仍显得啰嗦,于是scala里又引入了“上下文绑定”,
def foo[T](x: List[T]) (implicit m: Manifest[T])
可以简化为:
def foo[T:Manifest] (x: List[T])
在引入Manifest的时候,还引入了一个更弱一点的ClassManifest,所谓的弱是指类型信息不如Manifest那么完整,主要针对高阶类型的情况
scala在2.10里却用TypeTag替代了Manifest,用ClassTag替代了ClassManifest,原因是在路径依赖类型中,Manifest存在问题:
scala> class Foo{class Bar}
defined class Foo
scala> def m(f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar]) = ev
warning: there were 2 deprecation warnings; re-run with -deprecation for details
m: (f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar])Manifest[f.Bar]
scala> val f1 = new Foo;val b1 = new f1.Bar
f1: Foo = Foo@681e731c
b1: f1.Bar = Foo$Bar@271768ab
scala> val f2 = new Foo;val b2 = new f2.Bar
f2: Foo = Foo@3e50039c
b2: f2.Bar = Foo$Bar@771d16b9
scala> val ev1 = m(f1)(b1)
warning: there were 2 deprecation warnings; re-run with -deprecation for details
ev1: Manifest[f1.Bar] = Foo@681e731c.type#Foo$Bar
scala> val ev2 = m(f2)(b2)
warning: there were 2 deprecation warnings; re-run with -deprecation for details
ev2: Manifest[f2.Bar] = Foo@3e50039c.type#Foo$Bar
scala> ev1 == ev2 // they should be different, thus the result is wrong
res28: Boolean = true
了解之后,我们总结一下,TypeTag到底有啥用呢?看下面的例子:
请留意:
=:=,意思为:type equality
<:< ,意思为:subtype relation
类型判断不要用 == 或 !=
class Animal{}
class Dog extends Animal{}
object MainFoo extends App{
override def main(args: Array[String]): Unit = {
val list1 = List(1, 2, 3)
val list2 = List("1", "2", "3")
val list3 = List("1", "2", 3)
def test1(x: List[Any]) = {
x match {
case list: List[Int] => "Int list"
case list: List[String] => "String list"
case list: List[Any] => "Any list"
}
}
println(test1(list1))
println(test1(list2))
println(test1(list3))
import scala.reflect.runtime.universe._
def test2[A : TypeTag](x: List[A]) = typeOf[A] match {
case t if t =:= typeOf[String] => "String List"
case t if t <:< typeOf[Animal] => "Dog List"
case t if t =:= typeOf[Int] => "Int List"
}
println(test2(List("string")))
println(test2(List(new Dog)))
println(test2(List(1, 2)))
}
}
不能同时有多个上界或下界,变通的方式是使用复合类型
T <: A with B
T >: A with B
可以同时有上界和下界,如
T >: A <: B
这种情况下界必须写在前边,上界写在后边,位置不能反。同时A要符合B的子类型,A与B不能是两个无关的类型。
可以同时有多个view bounds
T <% A <% B
这种情况要求必须同时存在 T=>A的隐式转换,和T=>B的隐式转换。
class A{}
class B{}
implicit def string2A(s:String) = new A
implicit def string2B(s:String) = new B
def foo2[ T <% A <% B](x:T) = println("foo2 OK")
foo2("test")
可以同时有多个上下文界定
T : A : B
这种情况要求必须同时存在C[T]类型的隐式值,和D[T]类型的隐式值。
class C[T];
class D[T];
implicit val c = new C[Int]
implicit val d = new D[Int]
def foo3[ T : C : D ](i:T) = println("foo3 OK")
foo3(2)
类型约束,提供了限定类型的另一种方式,一共有3中关系声明:
T =:= U意思为:T类型是否等于U类型
T <:< U意思为:T类型是否为U或U的子类型
T <%< U意思为:T类型是否被隐式(视图)转换为U
如果想使用上面的约束,需要添加“隐式类型证明参数”比如:
class Pair5[T] (val first: T, val second: T)(implicit ev: T <:< Comparable[T]){}
使用举例:
import java.io.File
class Pair6[T](val first: T, val second: T) {
def smaller(implicit ev: T <:< Ordered[T]) = {
if(first < second) first else second
}
}
object Main6 extends App{
override def main(args: Array[String]): Unit = {
//构造Pair6[File]时,注意此时是不会报错的
val p6 = new Pair6[File](new File(""), new File(""))
//这就报错了
p6.smaller
}
}
术语:
英文
中文
示例
Variance
型变
Function[-T, +R]
Nonvariant
不变
Array[A]
Covariant
协变
Supplier[+A]
Contravariant
逆变
Consumer[-A]
Immutable
不可变
String
Mutable
可变
StringBuilder
其中,Mutable常常意味着Nonvariant,但是Noncovariant与Mutable分别表示两个不同的范畴。
即:可变的,一般意味着“不可型变”,但是“不可协变”和可变的,分别表示两个不同范畴。
型变(Variance)拥有三种基本形态:协变(Covariant), 逆变(Contravariant), 不变(Nonconviant),可以形式化地描述为:
一般地,假设类型C[T]持有类型参数T;给定两个类型A和B,如果满足A <: B,则C[A]与 C[B]之间存在三种关系:
如果C[A] <: C[B],那么C是协变的(Covariant);
如果C[A] :> C[B],那么C是逆变的(Contravariant);
否则,C是不变的(Nonvariant)。
Scala的类型参数使用+标识“协变”,-标识“逆变”,而不带任何标识的表示“不变”(Nonvariable):
trait C[+A] // C is covariant
trait C[-A] // C is contravariant
trait C[A] // C is nonvariant
如何判断一个类型是否有型变能力:
一般地,“不可变的”(Immutable)类型意味着“型变”(Variant),而“可变的”(Mutable)意味着“不变”(Nonvariant)。
其中,对于不可变的(Immutable)类型C[T]
如果它是一个生产者,其类型参数应该是协变的,即C[+T];
如果它是一个消费者,其类型参数应该是逆变的,即C[-T]。
