scala运行时反射
几个关联的帮助文档:
- Overview | Scala Documentation。Scala里反射的整体概况。
- [[http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html][TypeTags and Manifests | Scala Documentation]]。=Manifest=目前已经是=deprecated=的状态,运行时类型方式用=TypeTag=,即本文主要内容。
- [[http://docs.scala-lang.org/overviews/reflection/symbols-trees-types.html][Symbols, Trees, and Types | Scala Documentation]]。一些关于抽象语法树的内容,比较偏运行时反射。
- Environment, Universes, and Mirrors | Scala Documentation
- Thread Safety | Scala Documentation
- Annotations, Names, Scopes, and More | Scala Documentation
还有一些编译期反射的相关文档:
- http://docs.scala-lang.org/overviews/macros/overview.html
- http://docs.scala-lang.org/overviews/quasiquotes/intro.html
- http://docs.scala-lang.org/overviews/macros/bundles.html
- https://github.com/scalamacros/macrology201
以及一个相关的项目:Scalameta
scala.reflect.classTag
Scala Doc里的文档:
A ClassTag[T]
stores the erased class of a given type T
, accessible via the runtimeClass
field. This is particularly useful for instantiating Arrays whose element types are unknown at compile time. ClassTags are a weaker special case of scala.reflect.api.TypeTags#TypeTags
, in that they wrap only the runtime class of a given type, whereas a TypeTag contains all static type information. That is, ClassTags 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.
For example:
scala> def mkArray[T : ClassTag](elems: T*) = Array[T](elems: _*)
mkArray: [T](elems: T*)(implicit evidence$1: scala.reflect.ClassTag[T])Array[T]
scala> mkArray(42, 13)
res0: Array[Int] = Array(42, 13)
scala> mkArray("Japan","Brazil","Germany")
res1: Array[String] = Array(Japan, Brazil, Germany)
See scala.reflect.api.TypeTags for more examples, or the Reflection Guide: TypeTags for more details.
ClassTag比较简单。下面是ClassTag转Java里Class的一个例子:
implicit def toClass[A: ClassTag]: Class[A] = implicitly[ClassTag[A]]
.runtimeClass
.asInstanceOf[Class[A]]
这个方法和Scala里自带的=classOf=返回值是一样的:
test("ClassTag should work") {
assert(classOf[ReflectSuite] eq toClass[ReflectSuite])
}
但此处的=toClass=添加了=implicit=关键字,方便使用上下文边界的方式带入=Class[T]=参数。
剩余内容部分摘自 Reflection Guide: TypeTags 。
scala.reflect.api.TypeTags#TypeTag
There exist three different types of TypeTags:
scala.reflect.api.TypeTags#TypeTag
. A full type descriptor of a Scala type. For example, aTypeTag[List[String]]
contains all type information, in this case, of typescala.List[String]
.scala.reflect.ClassTag
. A partial type descriptor of a Scala type. For example, aClassTag[List[String]]
contains only the erased class type information, in this case, of typescala.collection.immutable.List
. ClassTags provide access only to the runtime class of a type. Analogous toscala.reflect.ClassManifest
.scala.reflect.api.TypeTags#WeakTypeTag
. A type descriptor for abstract types
=TypeTag=和=WeakTypeTag=的区别是前者保证了对应的类型是具体的,也就是其中没有出现类型参数或者抽象类型。Stackoverflow上有一个关于这个的回答:WeakTypeTag v. TypeTag
Like Manifests, TypeTags are always generated by the compiler, and can be obtained in three ways.
via the Methods=typeTag= ,=classTag=, or weakTypeTag
One can directly obtain a TypeTag for a specific type by simply using method typeTag, available through Universe. For example, to obtain a TypeTag which represents Int, we can do:
import scala.reflect.runtime.universe._
val tt = typeTag[Int]
Or likewise, to obtain a ClassTag which represents String, we can do:
import scala.reflect._
val ct = classTag[String]
Each of these methods constructs a TypeTag[T]
or ClassTag[T]
for the given type argument T.
Using an Implicit Parameter of Type TypeTag[T]
, ClassTag[T]
, or
WeakTypeTag[T]
As with Manifests, one can in effect request that the compiler generate a TypeTag. This is done by simply specifying an implicit evidence parameter of type TypeTag[T]
. If the compiler fails to find a matching implicit value during implicit search, it will automatically generate a TypeTag[T]
. Note: this is typically achieved by using an implicit parameter on methods and classes only. For example, we can write a method which takes some arbitrary object, and using a TypeTag, prints information about that object's type arguments:
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")
}
Here, we write a generic method paramInfo parameterized on T, and we supply an implicit parameter (implicit tag: TypeTag[T]
). We can then directly access the type (of type Type) that tag represents using method tpe of TypeTag. We can then use our method paramInfo as follows:
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)
Using a Context bound of a Type Parameter
A less verbose way to achieve exactly the same as above is by using a context bound on a type parameter. Instead of providing a separate implicit parameter, one can simply include the TypeTag in the type parameter list as follows:
def myMethod[T: TypeTag] = ...
Given context bound [T: TypeTag]
, the compiler will simply generate an implicit parameter of type TypeTag[T]
and will rewrite the method to look like the example with the implicit parameter in the previous section. The above example rewritten to use context bounds is as follows:
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)
Scala TypeTag转Java Type
可以把一个TypeTag转为Java里面的Type类型,这个当然只对部分比较简单的类型适用,毕竟Scala的类型系统比Java里的复杂得多:
def toJavaLangReflectType(tpe: TypeRef): Type = {
// (1)
val mirror = runtimeMirror(getClass.getClassLoader)
// (2)
val TypeRef(_, _, params) = tpe
// (3)
val clazz = mirror.runtimeClass(tpe) match {
// (4)
// wrap primitive type
case x if x eq classOf[Int] => classOf[JInteger]
case x if x eq classOf[Double] => classOf[JDouble]
case x if x eq classOf[Boolean] => classOf[JBoolean]
case x if x eq classOf[Long] => classOf[JLong]
case x => x
}
// (5)
if(params.isEmpty) { clazz } else {
// (6)
new ParameterizedType() {
override def getRawType: Type = clazz
override def getActualTypeArguments: Array[Type] =
params.map(x => toJavaLangReflectType(x.asInstanceOf[TypeRef])).toArray[Type]
override def getOwnerType: Type = null
}
}
}
以上的代码片段里要先把=universe=给import进来,并且import位于=java.lang=包下的=Type=接口:
import java.lang.reflect.{ParameterizedType, Type}
import java.lang.{
Boolean => JBoolean,
Double => JDouble,
Integer => JInteger,
Long => JLong
}
import scala.reflect.runtime.universe._
首先要注意(1)处,=TypeRef=表示的都还只是编译期的类型信息(更多的编译期反射信息可以参考一个guide:[[http://docs.scala-lang.org/overviews/reflection/symbols-trees-types.html][Reflection Guide: Symbols]],要构造出运行期的对象,必须使用=universe.runtimeMirror=方法获取一个=mirror=(:exclamation:这里还要好好看一下)。
(2)使用模式匹配获取类型参数,模式匹配返回值的头两个分别是类型所在的包、以及类型本身(:exclamation:这里说的很含糊),没用上因此直接用占位符了。
(3)处使用=mirror.runtimeClass(tpe)=获取运行时类型,(4)处过滤掉原生类型作为类型参数的情形(/scala里允许原生类型作为类型参数/)。(5)处和(6)处理了类型的参数,如果没有类型参数直接返回(3)处获取的运行时类型即可。如果有类型参数,则在(6)处返回一个实现=ParameterizedType=的匿名类。
一个例子(Gson在Scala里的反序列化)
使用上面提到的技巧,可以给Gson实现一个语法糖,/用类型来做方法参数/,最后效果形如:
// (1)
"[1,2,3]".deJsonTo[List[Int]]
// (2)
"[[1,2,3],[4,5],[6,7,8]]".deJsonTo[List[List[Int]]]
// (3)
"""{"a": 1, "b": 2}""".deJsonTo[Map[String, Int]]
// (4)
"""
{
"key1": [
{
"key11": [1, 2, 3],
"key12": [4, 5, 6],
"key13": [7, 8, 9]
},
{
"key11": [1, 2, 3],
"key12": [4, 5, 6],
"key13": [7, 8, 9]
}
],
"key2": [
{
"key21": [10, 11],
"key22": [13, 14]
}
]
}
""".deJsonTo[Map[String, List[Map[String, List[Int]]]]]
这样最主要的效果是,*反序列化所得的对象的类型将为=deJsonTo=方法中所指定的类型*,如上例中的(1),(2),(3),(4)处,对应的(编译期)类型将被推断为(这里的List和Map类型为Scala的集合类,这个和Java稍有不同)
List[Int]
List[List[Int]]
Map[String, Int]
Map[String, List[Map[String, List[Int]]]]
类似于Java里面的写法:
// (1)
new Gson().fromJson(/* 上例中(1)处的字符串 */,
new TypeToken<List<Integer>>(){}.getType());
// (2)
new Gson().fromJson(/* 上例中(2)处的字符串 */,
new TypeToken<List<List<Integer>>>(){}.getType());
//.(3)
new Gson().fromJson(/* 上例中(3)处的字符串 */,
new TypeToken<Map<String, Integer>>(){}.getType());
// (4)
new Gson().fromJson(/* 上例中(4)处的字符串 */,
new TypeToken<Map<String, List<Map<String, List<Integer>>>>>(){}.getType());
其实单独看=TypeToken=内部的泛型参数,里面的写法也不是很繁琐,但是*由于少了类型推断,Java里必须再把参数在声明变量时再写一遍:*
List<Integer> a = // ...
List<List<Integer>> b = // ...
Map<String, Integer> c = // ...
Map<String, List<Map<String, List<Integer>>>> d = // ...
具体实现
// (1)
object gson {
object implicits {
// (2)
implicit class JsonToObject(serialized: String) {
// (3)
def deJsonTo[T: TypeTag](implicit gson: Gson): T = {
// (4)
fromJson(serialized)
}
}
}
// (5)
def fromJson[T: TypeTag](serialized: String)(implicit gson: Gson): T = {
// (6)
gson.fromJson(
serialized,
// (7)
toJavaLangReflectType(implicitly[TypeTag[T]].tpe.asInstanceOf[TypeRef])
)
}
}
(1)处为方便,所有相关的方法和类放置于=gson=对象中。
(2)为为了支持后缀方法所引入的隐式转换类,相关文档可参考[[https://docs.scala-lang.org/tour/implicit-conversions.html][Implicit Conversions | Scala Documentation]]。(3)处所定义的方法=deJsonTo=,带有两个隐式参数:
- =gson: Gson=,直接由=implicit gson: Gson=指定
- =T: TypeTag=,这是一个由上下文边界指定的隐式参数,相关文档可参考What are Scala context bounds? | Scala Documentation。
(3)处的声明等价于:
def deJsonTo[T](implicit gson: Gson, typeTag: TypeTag[T]): T = // 具体实现
这是没有使用上下文边界的写法。(4)处直接调用具体的实现=fromJson=方法来对后缀方法解糖,同时传入两个隐式参数。这里的声明和=deJsonTo=基本一致。
声明中传入的隐式gson为Java里的Gson对象(这里由于同名,会掩盖掉外面定义的gson object)。传入隐式Gson对象是为了可以在使用时适配不同的自定义反序列化器,如此处默认定义的两个反序列化器:
gson.implicits.default
object gson {
object implicits {
// 其他代码
object default {
private val gsonBuilder: GsonBuilder = new GsonBuilder
// (1)
implicit def create: Gson = gsonBuilder.create()
}
}
// 其他代码
}
这里=default=对象同样按照惯例放置于=gson.implicits=中,注意(1)处定义所定义的隐式方法即可。这样实现后,在import:
import gson.implicits._
import gson.implicits.default._
后,按照如下方式调用:
// (1)
import java.util.{List => JList, Map => JMap}
val jObj = """
{
"key1": [
{
"key11": [1, 2, 3],
"key12": [4, 5, 6],
"key13": [7, 8, 9]
}
],
"key2": [
{
"key21": [10, 11],
"key22": [13, 14]
}
]
}
""".deJsonTo[JMap[String, JList[JMap[String, JList[Int]]]]] // (2)
(1)处将=java.util=中的=List=和=Map=做了个alias,否则会和scala的默认名字冲突。(2)处调用=deJsonTo=方法时,隐式转换为调用=JsonToObject=的=deJsonTo=方法,同时Scala编译器同时生成对应泛型参数的TypeTag隐式参数,并从=default._=对象中找到隐式=create=方法。
这里由于=default=对象中实际上并没有对=GsonBuilder=做任何定制,所以直接返回=new Gson=也是可以的:
object default {
implicit def create: Gson = new Gson
}
当然,在不是多线程的环境下,所有反序列化用一个=Gson=对象问题也不大(但毕竟小对象频繁创建问题也不大,Effective Java的[[http://www.informit.com/articles/article.aspx?p=1216151&seqNum=5][Item 5: Avoid creating unnecessary objects | Creating and Destroying Java Objects | InformIT]]提到:This item should not be misconstrued to imply that object creation is expensive and should be avoided. On the contrary, the creation and reclamation of small objects whose constructors do little explicit work is cheap, especially on modern JVM implementations. Creating additional objects to enhance the clarity, simplicity, or power of a program is generally a good thing.):
object default {
implicit val gson: Gson = new Gson
}
gson.implicits.default1
另一个反序列化器针对Scala的不可变容器做了类型适配:
object gson {
object implicits {
// 其他代码
object default1 {
import java.util.{List => JList, Map => JMap}
import java.lang.reflect.Type
import collection.JavaConverters._
private val gsonBuilder: GsonBuilder = new GsonBuilder
gsonBuilder.registerTypeAdapter(
classOf[List[_]],
new JsonDeserializer[List[_]] {
override def deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext): List[_] = {
val value = context.deserialize[JList[_]](json,
new ParameterizedType {
override def getRawType: Type = classOf[JList[_]]
override def getActualTypeArguments: Array[Type] =
typeOfT.asInstanceOf[ParameterizedType]
.getActualTypeArguments
override def getOwnerType: Type = null
})
// (1)
List(value.asScala: _*)
}
}
)
gsonBuilder.registerTypeAdapter(
classOf[Map[_, _]],
new JsonDeserializer[Map[_, _]] {
override def deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext): Map[_, _] = {
val value = context.deserialize[JMap[_, _]](json,
new ParameterizedType {
override def getRawType: Type = classOf[JMap[_, _]]
override def getActualTypeArguments: Array[Type] =
typeOfT.asInstanceOf[ParameterizedType].getActualTypeArguments
override def getOwnerType: Type = null
})
// (2)
value.asScala.toMap
}
}
)
implicit def create: Gson = gsonBuilder.create()
}
}
// 其他代码
}
其实=default1=里所定义的=gsonBuilder=,和在java里面的实现基本一样,不过是语法改了一下,注意一下(1)处和(2)处从java的集合类转换为scala的集合类即可。
回到=gson.fromJson=方法
再来看gson中的fromJson方法,最后的(7)处使用了前文所提到的=toJavaLangReflectType=方法,传入一个=TypeTag=,转换为Java里对应的Type。这样便完成整个反序列化的过程。
总结
Scala中的运行时反射,基本上思路和Java里的运行时反射一致,都是利用类文件中所保存的信息,不过有下面几点区别:
- scala中的类型系统要复杂的多,因此相比java里面简单的=Type=接口及其对应的子接口,scala里所需要用于表示类型信息的类和特征也要多一些。
- 相比java里面,传递类型信息需要手动传入匿名类。Scala里可以使用上下文边界传入编译器自动生成的=TypeTag=类型,这个简化代码的效果还挺明显,使得Scala里面看起来就像是*方括号是以类型为参数的方法调用,圆括号是实例为参数的方法调用*。
- 此处的反射大多其实是*运行期的编译器信息反射*,scala里面还可以在编译期做反射,以实现代码检查、代码生成等需求,这块大多是宏和quasiquote的相关内容。这一块在Java里面基本通过外部库来实现,如=aspectj=,=asm=等等,还有一些比较轻量级的,注解向的如=AutoValue=, =Lombok=等等。