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. :-)r
/**
* 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)
}
}
ref:https://my.oschina.net/cloudcoder/blog/856106