Reflection

TypeTags 和 Manifests

Language

与其他JVM语言一样,Scala的类型在运行时被擦除。这意味着,如果要检查某个实例的运行时类型,则可能无法访问Scala编译器在编译时可用的所有类型信息。

scala.reflect.ManifestTypeTags可以看作是将编译时可用的所有类型信息携带到运行时的对象。 例如,TypeTag[T]封装了某个编译时类型T的运行时类型表示。但是请注意,TypeTag应该被认为是对2.10之前的Manifest概念的更丰富的替代品,后者还与Scala反射完全集成。

有三种不同类型的类型标记:

  1. scala.reflect.api.TypeTags#TypeTag。 Scala类型的完整类型描述符。例如,TypeTag[List[String]]包含所有类型信息,在本例中是类型scala.List[String]

  2. scala.reflect.ClassTag。 Scala类型的部分类型描述符。例如,ClassTag[List[String]]只包含已擦除、关于类的类型信息,在本例中为scala.collection.immutable.ListClassTag只提供对类型的运行时类的访问。其类似于scala.reflect.ClassManifest

  3. scala.reflect.api.TypeTags#WeakTypeTag。 抽象类型的类型描述符(参见下面相应的小节)。

获取TypeTag

Manifest类似,TypeTag总是由编译器生成,可以通过三种方式获得。

通过方法typeTagclassTagweakTypeTag

通过使用通过Universe提供的方法typeTag,就可以直接获得特定类型的TypeTag

例如,要获取表示IntTypeTag,我们可以执行以下操作:

import scala.reflect.runtime.universe._
val tt = typeTag[Int]

或者类似地,要获得表示StringClassTag,我们可以执行以下操作:

import scala.reflect._
val ct = classTag[String]

这些方法中的每个方法都为给定的类型参数T构造一个TypeTag[T]ClassTag[T]

使用类型为TypeTag[T]ClassTag[T]WeakTypeTag[T]的隐式参数

Manifest一样,实际上可以 请求 编译器生成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")
}

这里,我们在T上编写了一个参数化的泛型方法paramInfo,并提供了一个隐式参数(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)

使用类型参数的上下文绑定

要实现与上述完全相同的效果,一种不太冗长的方法是在类型参数上使用上下文绑定。不需要提供单独的隐式参数,只需在类型参数列表中包含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(意思是TypeTag是继承自WeakTypeTag的),WeakTypeTag与普通的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 and Manifests

TypeTag可以大致对应2.10之前的scala.reflect.Manifest、 虽然scala.reflect.ClassTag对应于scala.reflect.ClassManifestscala.reflect.api.TypeTags#TypeTag主要对应于scala.reflect.Manifest,但其他2.10版之前的Manifest类型与2.10版的Tag类型没有直接对应关系。

  • 不支持scala.reflect.OptManifest。 这是因为Tag可以具体化任意类型,所以它们总是可用的。

  • 没有对应的scala.reflect.AnyValManifest。 取而代之的是,可以将其Tag与基本Tag之一(在相应的伴随对象中定义)进行比较,以找出其是否代表原始值类。此外,可以简单地使用<tag>.tpe.typeSymbol.isPrimitiveValueClass

  • 无法替换Manifest伴随对象中定义的工厂方法。 取而代之的是,可以使用Java(用于类)和Scala(用于类型)提供的反射API生成相应的类型。

  • 不支持某些manifest操作(即<:<, >:>typeArguments)。 取而代之的是,可以使用Java(用于类)和Scala(用于类型)提供的反射API。

在Scala 2.10中,不建议使用scala.reflect.ClassManifest,而推荐使用TypeTagClassTag,并且计划在即将发布的版本中弃用scala.reflect.Manifest。因此,建议迁移任何基于Manifest的API以使用Tag

Contributors to this page: