Scala类型参数(先了解即可)

mac2026-03-10  5

1  泛类型

类和特质都可以带类型参数,用方括号来定义类型参数,可以用类型参数来定义变量、方法参数和返回值。带有一个或多个类型参数的类是泛型的。如下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")

2  泛型函数

函数或方法也可以有类型(泛型)参数。

// 从参数类型来推断类型

println(getMiddle(Array("Bob", "had", "a", "little", "brother")).getClass.getTypeName)

//指定类型,并保存为具体的函数。

val f = getMiddle[String] _

println(f(Array("Bob", "had", "a", "little", "brother")))

3  类型变量限定

在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]。

5  视图界定

在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)

}

6  上下文界定

视图界定 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)

  }

}

7  Manifest上下文界定

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)))

  }

}

8  多重界定

不能同时有多个上界或下界,变通的方式是使用复合类型

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)

9  类型约束

类型约束,提供了限定类型的另一种方式,一共有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

  }

}

10  型变

术语:

英文

中文

示例

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]。

最新回复(0)