scala的reflect


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

ClassManifestManifest的一个弱化版本,保存的类型信息不如Manifest多, 而ClassTagTypeTag的一个弱化版本.在实际应用中,SparkContext大量使用ClassTag保存泛型信息,一般情况下ClassTag就满足我们使用。

5 总结

  1. ClassTag 可以获取擦除后的类型信息,再scala.reflect中,有classTagclassof两个主要方法

  2. TypeTag获取运行时的类型信息,在scala.reflect.runtime.universe中,有typeTagtypeOf两个主要方法。

  3. runtimeMirror(getClass.getClassLoader) 反射类reflectClass(), 反射实例reflect(),反射成员变量reflectField(),反射静态类staticClass

  4. 不论我们是去操作方法还是实例化对象或是操作属性,流程大致都一致,获取类镜像是必经之路,然后去做反射,最后操作。

  5. scala.reflect.runtime.universeruntimeMirror(getClass.getClassLoader)获取当前jvm中所有的的运行类镜像。

6 参考

  1. 一篇入门 – Scala 反射

作者: jdi146
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 jdi146 !
评论
评论
  目录