1 为什么需要reflect
Scala是基于JVM的语言, Scala编译器会将Scala代码编译成JVM字节码, 而JVM编译过程中JVM中的泛型并不会保存。下面这段代码并不会通过
class ScalaDemo[T] {
def makeTArray(): Array[T] = new Array[T](10)
}
那么问题来了,假如我们有一种对象可以保存泛型信息,不就解决了吗,那么解决办法来了,这些对象就是ClassTag 、Manifest、ClassManifest、TypeTag
反射作用三部曲:
捕获对象、泛型类型信息
根据捕获信息实例化新对象
通过实例操作对象的属性和方法
反射的种类:
1、scala.reflect.runtime.universe 运行时的反射
2、scala.reflect.marcos.universe编译期的反射
2 运行期时反射
我们重点讲解运行期反射:
Scala运行时类型信息是保存在TypeTag对象中, 编译器在编译过程中将类型信息保存到TypeTag中, 并将其携带到运行期. 通过TypeTag的 typeTag
方法获得需要的Type(如果不是从对象换取Type 而是从class中获得 可以直接用 typeOf[类名])
scala> val list = List(1,2,3)
val list: List[Int] = List(1, 2, 3)
scala> def getType[T:TypeTag](obj:T) = typeTag[T]
def getType[T](obj: T)(implicit evidence$1: reflect.runtime.universe.TypeTag[T]): reflect.runtime.universe.TypeTag[T]
scala> getType(list)
val res1: reflect.runtime.universe.TypeTag[List[Int]] = TypeTag[List[Int]]
----------------------------------------------------------------------------------
scala> def getType1[T: TypeTag](obj:T) = typeOf[T]
def getType1[T](obj: T)(implicit evidence$1: reflect.runtime.universe.TypeTag[T]): reflect.runtime.universe.Type
scala> getType1(list)
val res2: reflect.runtime.universe.Type = List[Int]
想要获得擦除后的类型信息, 可以使用ClassTag
scala> import scala.reflect._
import scala.reflect._
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> val tpeTag = typeTag[List[Int]]
tpeTag: reflect.runtime.universe.TypeTag[List[Int]] = TypeTag[scala.List[Int]]
scala> val clsTag = classTag[List[Int]]
clsTag: scala.reflect.ClassTag[List[Int]] = scala.collection.immutable.List
scala> clsTag.runtimeClass
res12: Class[_] = class scala.collection.immutable.List
scala> classOf[List[Int]]
res0: Class[List[Int]] = class scala.collection.immutable.List
3 反射的实际应用
3.1 运行时类型实例化
scala> case class Fruits(id: Int, name: String)
defined class Fruits
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
// 获得当前JVM中的所有类镜像
scala> val rm = runtimeMirror(getClass.getClassLoader)
rm: reflect.runtime.universe.Mirror = JavaMirror with scala.tools.nsc.interpreter.IMain$TranslatingClassLoader@566edb2e of .....
// 获得`Fruits`的类型符号, 并指定为class类型
scala> val classFruits = typeOf[Fruits].typeSymbol.asClass
classFruits: reflect.runtime.universe.ClassSymbol = class Fruits
// 根据上一步的符号, 从所有的类镜像中, 取出`Fruits`的类镜像
val cm = rm.reflectClass(classFruits)
cm: reflect.runtime.universe.ClassMirror = class mirror for Fruits (bound to null)
// 获得`Fruits`的构造函数, 并指定为asMethod类型
scala> val ctor = typeOf[Fruits].declaration(nme.CONSTRUCTOR).asMethod
ctor: reflect.runtime.universe.MethodSymbol = constructor Fruits
// 根据上一步的符号, 从`Fruits`的类镜像中, 取出一个方法(也就是构造函数)
scala> val ctorm = cm.reflectConstructor(ctor)
// 调用构造函数, 反射生成类实例, 完成
scala> ctorm(1, "apple")
res2: Any = Fruits(1,apple)
3.2 运行时类成员操作
scala> case class Fruits(id: Int, name: String)
defined class Fruits
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
// 获得当前JVM中的所有类镜像
scala> val rm = runtimeMirror(getClass.getClassLoader)
rm: reflect.runtime.universe.Mirror = JavaMirror with scala.tools.nsc.interpreter.IMain$TranslatingClassLoader@566edb2e of .....
// 生成一个`Fruits`的实例
scala> val fruits = Fruits(2, "banana")
fruits: Fruits = Fruits(2,banana)
// 根据`Fruits`的实例生成实例镜像
val instm = rm.reflect(fruits)
instm: reflect.runtime.universe.InstanceMirror = instance mirror for Fruits(2,banana)
// 获得`Fruits`中, 名字为name的成员信息, 并指定为asTerm类型符号
scala> val nameTermSymbol = typeOf[Fruits].declaration(newTermName("name")).asTerm
nameTermSymbol: reflect.runtime.universe.TermSymbol = value name
// 根据上一步的符号, 从`Fruits`的实例镜像中, 取出一个成员的指针
scala> val nameFieldMirror = instm.reflectField(nameTermSymbol)
nameFieldMirror: reflect.runtime.universe.FieldMirror = field mirror for private[this] val name: String (bound to Fruits(2,banana))
// 通过get方法访问成员信息
scala> nameFieldMirror.get
res3: Any = banana
// 通过set方法, 改变成员信息
scala> nameFieldMirror.set("apple")
// 再次查询, 发现成员的值已经改变, 即便是val, 在反射中也可以改变
scala> nameFieldMirror.get
res6: Any = apple
3.3 运行时方法操作
def getMethod(className: String, methodName: String): universe.MethodMirror = {
// 获取实例
val classInstance = Class.forName(className).newInstance()
// 实例镜像
val instanceMirror = runtimeMirror.reflect(classInstance)
// 全局可访问类符号
val classSymbol = runtimeMirror.staticClass(className)
// 方法符号
val methodSymbol = classSymbol.selfType.decl(scala.reflect.runtime.universe.TermName(methodName)).asMethod
// 反射
instanceMirror.reflectMethod(methodSymbol)
}
4 ClassTag 、Manifest、ClassManifest、TypeTag
scala在2.10用TypeTag替代了Manifest,用ClassTag替代了ClassManifest.原因是在路径依赖类型中,Manifest存在问题
下面这个实例中 mfun(f1)(b1) == mfun(f2)(b2)
理应是 false
,因为b1和b2依赖的外部实例是不一样的。
scala> class Foo{class Bar}
defined class Foo
scala> val f1 = new Foo;val b1 = new f1.Bar
f1: Foo = Foo@994f7fd
b1: f1.Bar = Foo$Bar@1fc0e258
scala> val f2 = new Foo;val b2 = new f2.Bar
f2: Foo = Foo@ecd59a3
b2: f2.Bar = Foo$Bar@15c882e8
scala> def mfun(f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar]) = ev
mfun: (f: Foo)(b: f.Bar)(implicit ev: scala.reflect.Manifest[f.Bar])scala.reflect.Manifest[f.Bar]
scala> def tfun(f: Foo)(b: f.Bar)(implicit ev: TypeTag[f.Bar]) = ev
tfun: (f: Foo)(b: f.Bar)(implicit ev: reflect.runtime.universe.TypeTag[f.Bar])reflect.runtime.universe.TypeTag[f.Bar]
scala> mfun(f1)(b1) == mfun(f2)(b2)
res14: Boolean = true
scala> tfun(f1)(b1) == tfun(f2)(b2)
res15: Boolean = false
ClassManifest
是Manifest
的一个弱化版本,保存的类型信息不如Manifest
多, 而ClassTag
是TypeTag
的一个弱化版本.在实际应用中,SparkContext大量使用ClassTag
保存泛型信息,一般情况下ClassTag
就满足我们使用。
5 总结
ClassTag
可以获取擦除后的类型信息,再scala.reflect
中,有classTag
和classof
两个主要方法
TypeTag
获取运行时的类型信息,在scala.reflect.runtime.universe
中,有typeTag
和typeOf
两个主要方法。
runtimeMirror(getClass.getClassLoader)
反射类reflectClass()
, 反射实例reflect()
,反射成员变量reflectField()
,反射静态类staticClass
…不论我们是去操作方法还是实例化对象或是操作属性,流程大致都一致,获取类镜像是必经之路,然后去做反射,最后操作。
scala.reflect.runtime.universeruntimeMirror(getClass.getClassLoader)
获取当前jvm中所有的的运行类镜像。