【scala关键字系列】TypeTags Manifests
文章目录
在Scala中,类型在运行时被擦除,这意味着如果你检查某个实例的运行时类型,可能无法获取编译时Scala编译器可用的所有类型信息。
与scala.reflect.Manifest
类似,**TypeTags
可以看作是将所有可用的类型信息从编译时传递到运行时的对象。**例如,TypeTag[T]
封装了某个编译时类型T的运行时类型表示。需要注意的是,TypeTags
应该被视为2.10之前Manifest
的更丰富的替代品,并且与Scala反射完全集成。
存在三种不同类型的TypeTags
:
-
scala.reflect.api.TypeTags#TypeTag
:Scala类型的完整类型描述符。例如,TypeTag[List[String]]
包含了List[String]
类型的所有类型信息。 -
scala.reflect.ClassTag
:Scala类型的部分类型描述符。例如,ClassTag[List[String]]
仅包含已擦除的类类型信息,即scala.collection.immutable.List
。ClassTags
只提供对类型的运行时类的访问。类似于scala.reflect.ClassManifest
。 -
scala.reflect.api.TypeTags#WeakTypeTag
:抽象类型的类型描述符(请参见下面的相应小节)。
获取TypeTag
与Manifests
一样,TypeTags
始终由编译器生成,并且可以通过三种方式获得。
1. 通过typeTag
、classTag
或weakTypeTag
方法
可以通过Universe
上可用的typeTag
方法直接获得特定类型的TypeTag
。
例如,要获得表示Int
的TypeTag
,可以这样做:
import scala.reflect.runtime.universe._
val tt = typeTag[Int]
同样地,要获得表示String
的ClassTag
,可以这样做:
import scala.reflect._
val ct = classTag[String]
每个方法都构造了一个TypeTag[T]
或ClassTag[T]
,其中T为给定的类型参数。
2. 使用隐式参数TypeTag[T]
、ClassTag[T]
或WeakTypeTag[T]
与Manifests
类似,可以在方法或类中指定一个隐式证据参数,以请求编译器生成TypeTag
。只需将类型参数列表中的一个隐式参数的类型设为TypeTag[T]
。如果编译器在隐式搜索期间找不到匹配的隐式值,它将自动生成一个TypeTag[T]
。
注意:这通常只能在方法和类上使用隐式参数。
例如,我们可以编写一个方法,该方法接受任意对象,并使用TypeTag
打印有关该对象类型参数的信息:
import scala.reflect.runtime.universe._
def paramInfo[T](x: T)(implicit tag: TypeTag[T]): Unit = {
val targs = tag.tpe match { case TypeRef(_, _, args) => args }
println(s"type of $x has type arguments $targs")
}
在这里,我们编写了一个泛型方法paramInfo
,参数化为T,并提供了一个隐式参数(implicit tag: TypeTag[T])
。我们可以直接使用TypeTag
的tpe
方法访问tag
所代表的类型(Type)。
然后,我们可以这样使用paramInfo
方法:
scala> paramInfo(42)
type of 42 has type arguments List()
scala> paramInfo(List(1, 2))
type of List(1, 2) has type arguments List(Int)
3. 使用类型参数的上下文边界
以更简洁的方式实现与上述完全相同的效果是使用类型参数的上下文边界。不需要提供单独的隐式参数,只需在类型参数列表中包含TypeTag
即可:
def myMethod[T: TypeTag] = ...
给定上下文边界[T: TypeTag]
,编译器将生成一个类型为TypeTag[T]
的隐式参数,并将方法重写为前一节中带有隐式参数的示例。
使用上下文边界重新编写上面的示例如下:
import scala.reflect.runtime.universe._
def paramInfo[T: TypeTag](x: T): Unit = {
val targs = typeOf[T] match { case TypeRef(_, _, args) => args }
println(s"type of $x has type arguments $targs")
}
scala> paramInfo(42)
type of 42 has type arguments List()
scala> paramInfo(List(1, 2))
type of List(1, 2) has type arguments List(Int)
WeakTypeTags
WeakTypeTag[T]
泛化了TypeTag[T]
。与常规的TypeTag
不同,它的类型表示中的组件可以是类型参数或抽象类型的引用。但是,WeakTypeTag[T]
尽可能具体,即如果引用的类型参数或抽象类型存在类型标签,则会将具体类型嵌入到WeakTypeTag[T]
中。
继续上面的示例:
def weakParamInfo[T](x: T)(implicit tag: WeakTypeTag[T]): Unit = {
val targs = tag.tpe match { case TypeRef(_, _, args) => args }
println(s"type of $x has type arguments $targs")
}
scala> def foo[T] = weakParamInfo(List[T]())
foo: [T]=> Unit
scala> foo[Int]
type of List() has type arguments List(T)
TypeTags和Manifests
TypeTags
在某种程度上对应于2.10之前scala.reflect.Manifests
的概念。而scala.reflect.ClassTag
对应于scala.reflect.ClassManifest
,scala.reflect.api.TypeTags#TypeTag
在很大程度上对应于scala.reflect.Manifest
,其他2.10之前的Manifest类型没有与2.10的“Tag”类型直接对应。
-
scala.reflect.OptManifest
不受支持。这是因为Tags可以表示任意类型,因此它们始终可用。 -
scala.reflect.AnyValManifest
没有等效物。相反,可以将标签与基本标签(定义在相应的伴生对象中)进行比较,以确定它是否表示原始值类。此外,还可以简单地使用<tag>.tpe.typeSymbol.isPrimitiveValueClass
。 -
Manifest伴生对象中定义的工厂方法没有替代方案。相反,可以使用Java(用于类)和Scala(用于类型)提供的反射API生成相应的类型。
-
某些manifest操作(例如,
<:<
、>:>
和typeArguments
)不受支持。可以使用Java(用于类)和Scala(用于类型)提供的反射API来代替。
在Scala 2.10中,scala.reflect.ClassManifest
已被弃用,并计划在即将发布的版本中将scala.reflect.Manifest
弃用,以便使用Tags。因此,建议将基于Manifest的API迁移到使用Tags。