hangscer

scala的反射

2018/02/08

  如果当初Scala没有选择基于Jvm,那么Scala是否还能取得今天的成就吗?Jvm为Scala带了稳健强大的性能,同时也无法避免类型擦除的约束。
  作为Jvm上的先进语言,Scala在生成字节码时,编译器附加了额外的类型信息,及时class的泛型参数被擦除了,scala仍然可以获取泛型信息。
主要存在三种api:

  • TypeTag,可获取一个类型的全部信息,包括高阶类型,比如List[List[List[String]]]类型。
  • ClassTag,可获取类型的部分信息。ClassTag[List[List[String]]],仅可得到类型擦除后的类的类型,也就是scala.collection.immutable.List
  • WeakTypeTag是可以用于获取抽象类型,比如def foo[A]=List.empty[A],想要获取这个抽象类型A,需要使用WeakTypeTag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
➜ scala
Welcome to Scala 2.12.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_141).
Type in expressions for evaluation. Or try :help.
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> val tt=typeTag[List[List[String]]]
tt: reflect.runtime.universe.TypeTag[List[List[String]]] = TypeTag[scala.List[scala.List[String]]]
scala> import scala.reflect._
import scala.reflect._
scala> val ct=classTag[Map[String,Int]]
ct: scala.reflect.ClassTag[Map[String,Int]] = scala.collection.immutable.Map
scala> ct.runtimeClass
res1: Class[_] = interface scala.collection.immutable.Map

利用编译器获取类型信息

  之前说过,scala编译器在编译器全部保存了相关的类型信息,仅仅需要借助隐式参数由编译器传入即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import scala.reflect.runtime.universe._
def typeInfo[T](x: T)(implicit tag: TypeTag[T]) = tag.tpe match {
case TypeRef(preType: Type, symbol: Symbol, typeParams: List[Type]) =>
println(s"preType.typeSymbol : ${preType.typeSymbol}")
println(s"preType : ${preType}")
println(symbol.fullName)
println(typeParams)
}
typeInfo(Map("1"->1))
//打印如下::
preType.typeSymbol : package immutable
preType : scala.collection.immutable.type
scala.collection.immutable.Map
List(java.lang.String, Int)

在以上程序中,preType是x对象的前缀路径的类型,这里的scala.collection.immutable是一个package。symbol就是参数x的符号信息,typeParams是x对应的类型的类型参数,对于Map[String,Int],那么其类型参数有java.lang.StringInt

  一句话解释scala的反射机制:编译时期额外类型信息,此类型信息可以用编码显式给出,也可以用编译器推断。以下的例子可以说明:

1
def getType[T](x: T)(implicit tag: TypeTag[T]): Type = tag.tpe

  现有一个函数getType(x),可以此获取某个对象x的类型信息。显式给出类型为Any,编译器则传入隐式tag:TypeTag[Any]对象。

1
2
3
4
println(getType[Any](List(1, 2, 3)))
//打印 Type[Any]
println(getType(List(1, 2, 3)))
//打印 Type[List[Int]]

Type之间的比较操作

  type之间的比较主要有三种类型:

  • 比较两个类型是否是继承关系
  • 判断两个类型之间的相等性
  • 给定某一确定的类型的成员方法或者属性

  现在有两个trait,分别是AB,且AB的父级。

1
2
trait A
trait B extends A
1
2
3
4
5
6
val aType = typeOf[A]
val bType = typeOf[B]
println(aType =:= bType) //false
println(aType <:< bType) //false
println(bType <:< aType) //true
println(aType <:< aType) //true

  对于给定的两个Type实例,<:<方法可以用于比较两个类型是否具有父子类型关系,作用与Java中的isInstance类似。

  当使用==比较两个类型时,类型别名与实际类型被认为是不同类型,而=:=会用最根本的类型去比较。

1
2
3
4
5
type PersonName=String
val t1= typeOf[PersonName]
val t2= typeOf[String]
t1 == t2 //false
t1 =:= t2 //true

  TypeTag可以等效的看作为scala2.10版本以前的ManifestClassTag也可以等效的看作为scala2.10版本之前的ClassManifest