本篇文章主要让你们理解什么是Scala的反射, 以及反射的分类, 反射的一些术语概念和一些简单的反射例子.php
咱们知道, Scala是基于JVM的语言, Scala编译器会将Scala代码编译成JVM字节码, 而JVM编译过程当中会擦除一些泛型
信息, 这就叫类型擦除(type-erasure ).html
而咱们开发过程当中, 可能须要在某一时刻得到类中的详细泛型
信息. 并进行逻辑处理. 这时就须要用的 这个概念. 经过反射咱们能够作到java
在Scala-2.10之前, 只能在Scala中利用Java的反射机制, 可是经过Java反射机制获得的是只是擦除后的类型信息, 并不包括Scala的一些特定类型信息. 从Scala-2.10起, Scala实现了本身的反射机制, 咱们能够经过Scala的反射机制获得Scala的类型信息。python
Scala 的反射分为两个范畴:es6
这二者之间的区别在于Environment
, 而Environment
又是由universe
决定的. 反射的另外一个重要的部分就是一个实体集合,而这个实体集合被称为mirror
,有了这个实体集合咱们就能够实现对须要反射的类进行对应的操做,如属性的获取,属性值得设置,以及对反射类方法的调用(其实就是成员函数的入口地址, 但请注意, 这只是个地址
)!编程
可能有点绕, 说的直白点就是要操做类方法或者属性就须要得到指定的mirror
,而mirror
又是从Environment
中得来的,而Environment
又是Universes
中引入的,而Universes
根据运行时和编译时又能够分为两个领域的.api
对于不一样的反射, 咱们须要引入不一样的Universes
ruby
import scala.reflect.runtime.universe._ // for runtime reflection import scala.reflect.macros.Universe._ // for compile-time reflection
由于反射须要编译原理基础, 而我学的其实也很差, 因此不作过多深刻的探讨, 这里主要说一下Scala 在 .scala
到 .class
再到装入 JVM 的这个过程.markdown
类Java程序之因此能实现跨平台, 主要得益于JVM(Java Virtual Machine)的强大. JVM为何能实现让类Java代码能够跨平台呢? 那就要从类Java程序的整个编译、运行的过程提及.数据结构
咱们平时所写的程序, 都是基于语言(第三代编程语言)范畴的. 它只能是开发者理解, 但底层硬件(如内存和cpu)并不能读懂并执行. 所以须要经历一系列的转化. 类Java的代码, 首先会通过本身特有的编辑器, 将代码转为.class
, 再由ClassLoader将.class
文件加载到JVM运行时数据区, 此时JVM就能够读懂.class
的二进制文件, 并调用C/C++
来间接操做底层硬件, 实现代码功能.
首先Scala编译器要读取源代码, 一个字节一个字节地读进来, 找到关键词如if
、for
、while
等, 这就是词法分析的过程. 这个过程结束之后.Scala
代码就变成了规范的Token流, 就像咱们把一句话中的名词、动词、标点符号分辨出来.
接着Scala编译器就是对Token流进行语法分析了, 好比if后面跟着的是否是一个布尔型的变量, 并将符合规范的语法使用语法树存贮起来. 之因此要用语法树来存储, 是由于这样作能够方便之后对这棵树按照新的规则从新组织, 这也是编译器的关键所在.
以后Scala编译器会进行语义分析. 由于能够保证造成语法树之后不存在语法错误, 但语义是否正确则没法保证. 还有就是Scala会有一些相对复杂的语法, 语义分析器的做用就是将这些复杂的语法翻译成更简单的语法, 好比将foreach
翻译成简单的for循环, 使它更接近目标语言的语法规则.
最后就是由代码生成器将将语义分析的结果生成符合JVM规范的字节码了.
Symbol Table, 简单的来讲是用于编译器或者解释器的一种数据结构, 一般是用HashTable实现. 它所记载的信息一般是标识符(identifier)的相关信息,如类型,做用域等。那么,它一般会在语义分析(Semantic Analysis)阶段运用到.
语法树经过树结构来描述开始符到产生式的推导过程.
在计算机科学中,抽象语法树(abstract syntax tree 或者缩写为 AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。树上的每一个节点都表示源代码中的一种结构。之因此说语法是「抽象」的,是由于这里的语法并不会表示出真实语法中出现的每一个细节。
Scala运行时类型信息是保存在TypeTag对象中, 编译器在编译过程当中将类型信息保存到TypeTag中, 并将其携带到运行期. 咱们能够经过typeTag方法获取TypeTag类型信息。
举例以下:
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> typeTag[List[Int]] res0: reflect.runtime.universe.TypeTag[List[Int]] = TypeTag[scala.List[Int]] scala> res0.tpe res1: reflect.runtime.universe.Type = scala.List[Int] scala> typeOf[List[Int]] res2: reflect.runtime.universe.Type = scala.List[Int]
可是,上面的例子不实用, 由于咱们要在事先知道它的类型信息. 不过不要紧, 下面咱们写个方法, 来事先任意变量的类型获取:
scala> val ru = scala.reflect.runtime.universe
ru: scala.reflect.api.JavaUniverse = scala.reflect.runtime.JavaUniverse@6dae22e7
scala> def getTypeTag[T : ru.TypeTag](obj: T) = ru.typeOf[T] getTypeTag: [T](obj: T)(implicit evidence$1: ru.TypeTag[T])ru.TypeTag[T] scala> val list = List(1, 2, 3) list: List[Int] = List(1, 2, 3) scala> val theType = getTypeTag(list) theType: ru.Type = List[Int]
上面代码就是一个简单的方法定义, 可是用到了两个关键的技术, 分别是泛型[T]
和上下文界定[T : ru.TypeTag]
, 下面我来解读一下:
scala中的泛型称为类型参数化(type parameterlization).
经过名字, 应该有个模糊的概念了, 好像是要传一个类型当作参数. 举个例子:
scala> def getList[T](value: T) = List(value) getList: [T](value: T)List[T] scala> getList[Int](1) res4: List[Int] = List(1) scala> getList("1") res5: List[String] = List(1)
看例子, 咱们定义了一个方法, 根据传入的一个参数, 来生成一个List, 但为了方法的更加通用(能够处理任何类型), 咱们的参数类型(value: T)
使用了泛型T
, 可使任意字母, 但推荐大写(约定俗成).
这样, 咱们调用方法, 你们能够看到. 这个方法既能够传入Int
类型也能够传入String
类型. 而类型参数[]中的类型, 咱们能够手动填写, 也能够不填, Scala编译器会为咱们自动推导, 若是填写了, 编译器就不会进行类型推导, 而是进行类型检查, 看咱们的入参, 是否符合类型参数.
scala> getList[String](1) <console>:16: error: type mismatch; found : Int(1) required: String getList[String](1)
知识延伸 上界
<:
, 下界>:
, 视界<%
, 边界:
, 协变+T
, 逆变-T
trait Function1[-T, +U] { def apply(x: T): U } 等价于: fun: -T => +U
了解了泛型, 下面咱们说一下上下文界定[T : ru.TypeTag]
, 也叫作边界.
了解边界, 咱们先说一下视界<%
. 先举例子:
scala> case class Fruits(name: String)
defined class Fruits
scala> def getFruits[T <% Fruits](value: T): Fruits = value getFruits: [T](value: T)(implicit evidence$1: T => Fruits)Fruits scala> implicit val border: String => Fruits = str => Fruits(str) border: String => Fruits = <function1> scala> getFruits("apple") res11: Fruits = Fruits(apple)
能够看到, 在代码最后, 咱们调用方法getFruits
的时候,传入的是一个String
, 而返回给咱们的是一个Fruits(apple)
, 这是怎么作的的呢?
这就依赖于视界T <% Fruits
, 这个符号, 能够理解成, 当前名称空间, 必须存在一个implicit
能够将非继承关系的两个实体(String
到 Fruits
)的转换. 也就是咱们上面的那个隐式函数.
理解了视界, 那么边界就很简单了. 仍是先写个例子:
scala> case class Fruits[T](name: T) defined class Fruits scala> def getFruits[T](value: T)(implicit fun: T => Fruits[T]): Fruits[T] = value getFruits: [T](value: T)(implicit fun: T => Fruits[T])Fruits[T] scala> implicit val border: String => Fruits[String] = str => Fruits(str) border: String => Fruits[String] = <function1> scala> getFruits("apple") res0: Fruits[String] = Fruits(apple)
这个应该能够看懂吧, 有个T => Fruits[T]
的隐式转换. 而Scala有个语法糖能够简化上面的函数定义
def getFruits[T : Fruits](value: T): Fruits[T] = value
做用同样, 须要一个T => Fruits[T]
的隐式转换.
如今咱们在回头看一下最初的例子:
scala> def getTypeTag[T : ru.TypeTag](obj: T) = ru.typeOf[T]
这里, 咱们把Fruits
换成了 ru.TypeTag
, 可是咱们并无看到有T => TypeTag[T]
的隐式转换啊!
不要急, 听我说, 由于前面已经讲过了, Scala运行时类型信息是保存在TypeTag对象中, 编译器在编译过程当中将类型信息保存到TypeTag中, 并将其携带到运行期. 也就是说, Scala编译器会自动为咱们生成一个隐式, 只要咱们在方法中定义了这个边界.
一旦咱们获取到了类型信息(Type instance),咱们就能够经过该Type对象查询更详尽的类型信息.
scala> val decls = theType.declarations.take(10) decls: Iterable[ru.Symbol] = List(constructor List, method companion, method isEmpty, method head, method tail, method ::, method :::, method reverse_:::, method mapConserve, method ++)
而若是想要得到擦除后的类型信息, 可使用ClassTag
,
注意,
classTag
在包scala.reflect._
下
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
反射是比Scala自己更接近底层的一种技术, 因此固然比自己能够作更多的事情, 咱们试着使用反射, 在运行期生成一个实例:
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)
Mirror是按层级划分的,有
话很少说, 举例来看:
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
Manifest
的路径依赖问题Scala 在2.10以前的反射中, 使用的是 Manifest
和 ClassManifest
.
不过scala在2.10里却用TypeTag替代了Manifest,用ClassTag替代了ClassManifest.
缘由是在路径依赖类型中,Manifest存在问题:
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
请参考: