Scala中Manifest、ClassTag、TypeTag的学习

原创
2017/03/10 11:10
阅读数 1W

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中有隐式转换、隐式参数的功能,在这个地方可以用隐式参数来减轻调用者的麻烦。

这里给出了一个例子摘自 StackOverflow :
def foo[T](x: List[T])(implicit m: Manifest[T]) = {
    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
隐式参数m是由编译器根据上下文自动传入的,比如上面是编译器根据 "one","two" 推断出 T 的类型是 String,
从而隐式的传入了一个Manifest[String]类型的对象参数,使得运行时可以根据这个参数做更多的事情。

不过上面的foo 方法定义使用隐式参数的方式,仍显得啰嗦,于是scala里又引入了“上下文绑定”,
回顾一下之前 context bounds,使得foo方法
def foo[T](x: List[T]) (implicit m: Manifest[T])
可以简化为:
def foo[T:Manifest] (x: List[T])
这个机制起因是scala2.8对数组的重新设计而引入的,原本只是为了解决数组的问题

Manifest与ClassManifest的区别

后续被用在更多方面。在引入Manifest的时候,还引入了一个更弱一点的ClassManifest,
所谓的弱是指类型信息不如Manifest那么完整,主要针对高阶类型的情况:
scala> class A[T]
scala> val m = manifest[A[String]]
scala> val cm = classManifest[A[String]]
根据规范里的说法,m的信息是完整的:m: Manifest[A[String]] = A[java.lang.String],
但 cm 则只有 A[_] 即不包含类型参数的信息,
但我在2.10下验证cm也是:cm: ClassManifest[A[String]] = A[java.lang.String]

在获取类型其类型参数时也是都包含的:
scala> m.typeArguments
res8: List[scala.reflect.Manifest[_]] = List(java.lang.String)
scala> cm.typeArguments
res9: List[scala.reflect.OptManifest[_]] = List(java.lang.String)
案例如下:
scala> class A[B] //A[+B]  A[-B] 也是同样的效果
defined class A
scala> manifest[A[_]]
res15: scala.reflect.Manifest[A[_]] = A[_ <: Any]
scala> classManifest[A[_]]
res16: scala.reflect.ClassTag[A[_]] = A[<?>]
到这里我们基本明白了 Manifest 与 ClassManifest的区别

Manifest存在的问题


不过scala在2.10里却用TypeTag替代了Manifest,用ClassTag替代了ClassManifest,
原因是在路径依赖类型中,Manifest存在问题:
scala> class Foo{class Bar}
scala> def m(f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar]) = ev

scala> val f1 = new Foo;val b1 = new f1.Bar
scala> val f2 = new Foo;val b2 = new f2.Bar


scala> val ev1 = m(f1)(b1)
ev1: Manifest[f1.Bar] = Foo@681e731c.type#Foo$Bar

scala> val ev2 = m(f2)(b2)
ev2: Manifest[f2.Bar] = Foo@3e50039c.type#Foo$Bar

scala> ev1 == ev2 // they should be different, thus the result is wrong
res28: Boolean = true
ev1 不应该等于 ev2 的,因为其依赖路径(外部实例)是不一样的。将Manifest替换为TypeTag,结果就是正确的,需要引入import scala.reflect.runtime.universe._

Manifest->TypeTag ClassTag->ClassManifest


还有其他因素(见下面的引用),所以在2.10版本里,使用 TypeTag 替代了 Manifest
Manifests are a lie. It has no knowledge of variance (assumes all type parameters are co-variants),
and it has no support for path-dependent, existential or structural types.

TypeTags are types as the compiler understands them. Not “like” the compiler understands them,
but “as” the compiler understands them — the compiler itself use TypeTags. It’s not 1-to-1, it’s just 1. :-)

代码示例

​/**
 * A `ClassTag[T]` stores the erased class of a given type `T`, accessible via the `runtimeClass`
 * field. This is particularly useful for instantiating `Array`s whose element types are unknown
 * at compile time.
 * 泛型对象在运行的时候,它的T是被擦除的。ClassTag[T]存储的是给定类型的T,我们通过runtimeClass来访问
 * 这个泛型在运行时指定的对象。在实例化Array的时候,这个特别有用。在构建数组但是它的元素类型不知道
 * 的时候。在编译时是数组的元素类型是不知道的(运行时知道)。
 * `ClassTag`s are a weaker special case of [[scala.reflect.api.TypeTags#TypeTag]]s, in that they
 * wrap only the runtime class of a given type, whereas a `TypeTag` contains all static type
 * information. That is, `ClassTag`s are constructed from knowing only the top-level class of a
 * type, without necessarily knowing all of its argument types. This runtime information is enough
 * for runtime `Array` creation.
 * ClassTag是比TypeTag更弱的一种情况。ClassTag只包含了运行时给定的类的类别信息。而TypeTag不仅包含类
 * 的类别信息,还包含了所有静态的类信息。我们在绝大多数情况下会使用ClassTag,因为ClassTag告诉我们运
 * 行时实际的类型已经足够我们在做泛型的时候去使用了。
 *
 * 数组本身是泛型。而如果我们想创建一个泛型数组的话,理论上是不可以的。
 * 在Scala中运行时,数组必须要有具体的类型,如果你继续是泛型的话,会提示你
 * 具体的类型没有,无法创建相应的数组,这是个很大的问题!
 * 在Scala中引入了ClassTag。有了它,我们就可以创建一个泛型数组。
 *
 * 我们要构建泛型对象,这里是泛型数组。我们需要ClassTag来帮我们存储T的实际的类型。
 * 在运行时我们就能获取这个实际的类型。
 * */

import scala.reflect.ClassTag
import scala.reflect.runtime.universe._
class MyType[T]

object SimpleDemo {
  def arrayMake[T: ClassTag](first: T, second: T) = {
    val r = new Array[T](2)
    r(0) = first
    r(1) = second
    r
  }
  //上面的写法其实与下面的写法可以认为是等价的。下面的写法是一种更原生的写法。不建议使用下面的写法
  def arrayMake2[T](first: T, second: T)(implicit m:  ClassTag[T]) = {
    println(m.typeArguments)
    //打印泛型的实际类型
    println(implicitly[ClassTag[T]].runtimeClass)

    val r = new Array[T](2)
    r(0) = first
    r(1) = second
    r
  }

  //implicit m: ClassTag[T] 改成implicit m: Manifest[T]也是可以的
  def manif[T](x: List[T])(implicit m: Manifest[T]) = {
    if (m <:< manifest[String])
      println("List strings")
    else
      println("Some other type")
  }

  def manif2[T](x: List[T])(implicit m: ClassManifest[T]) = {
    //classManifest比manifest获取信息的能力更弱一点
    if (m <:< classManifest[String])
      println("List strings")
    else
      println("Some other type")
  }
  
  //implicit m: ClassTag[T] 改成implicit m: TypeTag[T]也是可以的
  def manif3[T](x: List[T])(implicit m: TypeTag[T]) = {
    println(x)
  }

  //ClassTag是我们最常用的。它主要在运行时指定在编译时无法确定的类别的信息。
  // 我这边 Array[T](elems: _*) 中的下划线是占位符,表示很多元素
  def mkArray[T: ClassTag](elems: T*) = Array[T](elems: _*)

  
  def main(args: Array[String]) {
    arrayMake(1, 2).foreach(println)
    arrayMake2(1, 2).foreach(println)
    manif(List("Spark", "Hadoop"))
    manif(List(1, 2))
    manif(List("Scala", 3))
    manif2(List("Spark", "Hadoop"))
    manif2(List(1, 2))
    manif2(List("Scala", 3))
    
    manif3(List("Spark", "Hadoop"))
    manif3(List(1, 2))
    manif3(List("Scala", 3))

    val m = manifest[MyType[String]]
    println(m) //myscala.scalaexercises.classtag.MyType[java.lang.String]
    val cm = classManifest[MyType[String]]
    println(cm) //myscala.scalaexercises.classtag.MyType[java.lang.String]

    /*
    其实manifest是有问题的,manifest对实际类型的判断会有误(如依赖路径),所以后来推出了ClassTag和TypeTag,
    用TypeTag来替代manifest,用ClassTag来替代classManifest
    */
    mkArray(1, 2, 3, 4, 5).foreach(println)
  }
}

 

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
5 收藏
0
分享
返回顶部
顶部