Reflection 是一种程序检查,甚至多是自我修改的能力。 它在面向对象、函数式和逻辑编程范例方面有着悠久的历史。虽然只有一些语言是以反射为指导原则,但随着时间的推移,许多语言逐渐发展出反射能力。html
反射涉及到对程序的其余隐含元素进行具体化(即明确表达)的能力。 这些元素能够是静态程序元素,如类、方法或表达式,也能够是动态元素,如当前的延续或完成事件,如方法调用和字段访问。 一般从根据执行反射过程的时间区分编译时和运行时反射。 编译时反射是开发程序转换器和生成器的强大方式,而运行时反射一般用于调整语言语义或支持软件组件之间的后期绑定。java
Scala直到2.10这个版本也尚未任何反射工具。而代替方式是,使用Java反射API的一部分,即处理提供动态检查类和对象并访问其成员的能力。然而,在独立Java反射下,许多Scala特定的元素是不可恢复的,它仅公开Java元素(没有函数,没有特质)和类型(没有存在判别、高阶特性、路径依赖和抽象类型)。另外,Java反射也没法恢复编译时通用的Java类型的运行时类型信息、经过运行时反射到Scala中的泛型类型的限制。es6
在Scala 2.10中,引入了一个新的反射库,不只解决了Java在Scala特定类型和泛型类型上运行时反射的缺点,并且为Scala增长了一个更强大的通用反射功能工具箱。除了Scala类型和泛型的全功能运行时反射以外,Scala 2.10还提供了以宏的形式的编译时反射功能,以及将Scala表达式变为抽象语法树的功能。编程
什么是运行时反射? 在运行时给定某个对象的类型或实例,反射就是可以:api
让咱们来看看如何经过几个例子来完成上述每一步。闭包
与其余JVM语言同样,Scala的类型在编译时被擦除。这意味着若是您要检查某个实例的运行时类型,则可能没法访问Scala编译器在编译时可用的全部类型信息。ide
TypeTag
能够被认为是在编译时和运行时携带全部类型信息的对象。不过,重要的是要注意TypeTag
老是由编译器生成的。 不管什么时候使用须要TypeTag
的隐式参数或上下文绑定,都会触发此周期。 这意味着,一般只能使用隐式参数或上下文边界得到TypeTag
。函数
例如,使用上下文边界:工具
scala> import scala.reflect.runtime.{universe => ru} import scala.reflect.runtime.{universe=>ru} scala> val l = List(1,2,3) l: List[Int] = List(1, 2, 3) scala> def getTypeTag[T: ru.TypeTag](obj: T) = ru.typeTag[T] getTypeTag: [T](obj: T)(implicit evidence$1: ru.TypeTag[T])ru.TypeTag[T] scala> val theType = getTypeTag(l).tpe theType: ru.Type = List[Int]
在上述代码中,咱们首先引入了 scala.reflect.runtime.universe
(为了使用 TypeTag
必须引入),而且咱们建立一个名为 l
的 List[Int]
。而后,咱们定义了一个方法 getTypeTag
,它有一个具备上下文绑定的类型参数 T
(正如REPL所示,这至关于定义了一个隐含的“根据”参数,这会致使编译器为 T
生成 TypeTag
)。最后,咱们用 l
做为参数调用咱们的方法,并调用返回 TypeTag
中包含的类型的 tpe
。 正如咱们所看到的,咱们获得了正确的完整类型(包括 List
的具体类型参数),List [Int]
。测试
一旦咱们得到了所需的 Type
实例,咱们就能够检查它,例如:
scala> val decls = theType.decls.take(10) decls: Iterable[ru.Symbol] = List(constructor List, method companion, method isEmpty, method head, method tail, method ::, method :::, method reverse_:::, method mapConserve, method ++)
经过反射得到的类型能够经过使用适当的 “调用者” mirror 调用它们的构造函数来实例化(mirror 在下面展开)。 让咱们经过一个使用REPL的示例:
scala> case class Person(name: String) defined class Person scala> val m = ru.runtimeMirror(getClass.getClassLoader) m: scala.reflect.runtime.universe.Mirror = JavaMirror with ...
第一步,咱们得到一个mirror m
,它使得当前类加载器加载的全部类和类型均可用,包括 Person
类。
scala> val classPerson = ru.typeOf[Person].typeSymbol.asClass classPerson: scala.reflect.runtime.universe.ClassSymbol = class Person scala> val cm = m.reflectClass(classPerson) cm: scala.reflect.runtime.universe.ClassMirror = class mirror for Person (bound to null)
第二步,经过使用 reflectClass
方法获取 Person
类的 ClassMirror
。 ClassMirror
提供对 Person
类的构造函数的访问。
scala> val ctor = ru.typeOf[Person].decl(ru.termNames.CONSTRUCTOR).asMethod ctor: scala.reflect.runtime.universe.MethodSymbol = constructor Person
Persons
构造函数的 symbol 能够经过在 Person
类型的声明中查找来仅使用运行时universe ru
来得到。
scala> val ctorm = cm.reflectConstructor(ctor) ctorm: scala.reflect.runtime.universe.MethodMirror = constructor mirror for Person.<init>(name: String): Person (bound to null) scala> val p = ctorm("Mike") p: Any = Person(Mike)
一般,运行时类型的成员可使用适当的“调用者” mirror 来访问(mirror 在下面展开)。让咱们经过一个使用REPL的示例:
scala> case class Purchase(name: String, orderNumber: Int, var shipped: Boolean) defined class Purchase scala> val p = Purchase("Jeff Lebowski", 23819, false) p: Purchase = Purchase(Jeff Lebowski,23819,false)
在这个例子中,咱们将尝试以反射的方式获取并设置 Purchase p
的 shipped
字段。
scala> import scala.reflect.runtime.{universe => ru} import scala.reflect.runtime.{universe=>ru} scala> val m = ru.runtimeMirror(p.getClass.getClassLoader) m: scala.reflect.runtime.universe.Mirror = JavaMirror with ...
正如咱们在前面的例子中所作的那样,咱们首先得到一个mirror m
,它使得全部可用的类和类型均可用,这些类和类型由类加载器加载,该类也加载了类 p
(Purchase
),咱们须要它才能访问成员 shipped
。
cala> val shippingTermSymb = ru.typeOf[Purchase].decl(ru.TermName("shipped")).asTerm shippingTermSymb: scala.reflect.runtime.universe.TermSymbol = method shipped
咱们如今查看 shipped
字段的声明,它给了咱们一个 TermSymbol
(一种 Symbol
)。稍后咱们须要使用这个Symbol
来得到一个镜像,使咱们能够访问该字段的值(对于某些实例)。
scala> val im = m.reflect(p) im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for Purchase(Jeff Lebowski,23819,false) scala> val shippingFieldMirror = im.reflectField(shippingTermSymb) shippingFieldMirror: scala.reflect.runtime.universe.FieldMirror = field mirror for Purchase.shipped (bound to Purchase(Jeff Lebowski,23819,false))
为了访问特定实例的 shipped
成员,咱们须要一个 mirror 用于咱们的特定实例,p
的实例镜像 im
。 考虑到咱们的实例 mirror,咱们能够为表示 p
类型字段的任意 TermSymbol
获取 FieldMirror
。
如今咱们为特定领域提供了一个 FieldMirror
,咱们可使用方法 get
和 set
来获取/设置特定实例的 shipped
成员。 让咱们将 shipped
状态更改成 true
。
scala> shippingFieldMirror.get res7: Any = false scala> shippingFieldMirror.set(true) scala> shippingFieldMirror.get res9: Any = true
那些习惯使用Java反射在运行时得到Java类实例的人可能已经注意到,在Scala中,咱们改成得到运行时类型。
下面的REPL运行显示了一个很是简单的场景,在Scala类上使用Java反射可能会返回使人惊讶或不正确的结果。
首先,咱们用一个抽象类型成员 T
定义一个基类 E
,并从中得出两个子类 C
和 D
。
scala> class E { | type T | val x: Option[T] = None | } defined class E scala> class C extends E defined class C scala> class D extends C defined class D
而后,咱们对 C
和 D
都各自建立一个实例,同时使类型成员 T
具体(在两种状况下都是 String
)
scala> val c = new C { type T = String } c: C{type T = String} = $anon$1@7113bc51 scala> val d = new D { type T = String } d: D{type T = String} = $anon$1@46364879
如今,咱们使用来自 Java Reflection 的 getClass
和 isAssignableFrom
获取表示 c
和 d
运行时类的 java.lang.Class
实例,而后测试运行时类 d
是否为 c
的运行时表示的子类。
scala> c.getClass.isAssignableFrom(d.getClass) res6: Boolean = false
在上面的代码中,咱们看到 D
扩展 C
,但最后的结果有点使人惊讶。在执行这个简单的运行时类型检查时,人们会认为问题“c
的子类是否为d
”的结果是 true 。可是,正如你可能在上面已经注意到的那样,当 c
和 d
被实例化时,Scala编译器实际上分别建立了 C
和 D
的匿名子类。这是因为 Scala 编译器必须将 Scala 特定的(即非Java)语言特性翻译成 Java 字节码中的某些等价物以便可以在JVM上运行。所以,Scala编译器常常在运行时建立用来代替用户定义的类的合成类(即自动生成的类)。这在Scala中至关广泛,而且在使用具备多个Scala功能的Java反射时能够观察到,例如,闭包、类型成员、类型优化、本地类等。
在这些状况下,咱们可使用 Scala 反射来获取这些 Scala 对象的精确运行时类型。Scala 运行时类型携带编译时的全部类型信息,避免编译时和运行时之间的这些类型不匹配。
下面,咱们定义一个使用 Scala 反射来获取其参数的运行时类型的方法,而后检查二者之间的子类型关系。若是其第一个参数的类型是其第二个参数类型的子类型,则返回 true
。
scala> import scala.reflect.runtime.{universe => ru} import scala.reflect.runtime.{universe=>ru} scala> def m[T: ru.TypeTag, S: ru.TypeTag](x: T, y: S): Boolean = { | val leftTag = ru.typeTag[T] | val rightTag = ru.typeTag[S] | leftTag.tpe <:< rightTag.tpe | } m: [T, S](x: T, y: S)(implicit evidence$1: scala.reflect.runtime.universe.TypeTag[T], implicit evidence$2: scala.reflect.runtime.universe.TypeTag[S])Boolean scala> m(d, c) res9: Boolean = true
正如咱们所看到的,咱们如今获得了预期的结果——d
的运行时类型的确是 c
的运行时类型的子类型。
Scala反射启用了一种元编程形式,可让程序在编译时自行修改。 这种编译时反射是以宏的形式实现的,它提供了在编译时执行操纵抽象语法树的方法的能力。
宏的一个特别有趣的方面是它们基于 Scala 的运行时反射所使用的相同的API,在包 scala.reflect.api
中提供。 这样能够在使用运行时反射的宏和实现之间共享通用代码。
请注意,宏指南侧重于宏特性,而本指南重点介绍反射API的通常方面。不过,许多概念直接应用于宏,例如抽象语法树,这些将在关于符号,树和类型的章节中详细讨论。
全部反射任务都须要创建适当的环境。这个环境根据反射任务是在运行时仍是在编译时完成而不一样。在运行时或编译时使用的环境之间的区别被封装在一个所谓的 universe 中。反射环境的另外一个重要方面是咱们能够反射访问的一组实体。这组实体由所谓的 mirror 肯定。
mirror 不只肯定可反射访问的一组实体。 他们还提供对这些实体进行反射的操做。 例如,在运行时反射中,调用者 mirror 可用于调用类的方法或构造函数。
Universe
是 Scala 反射的入口点。Universe
为反射中使用的全部主要概念(如 type
,Tree
和 Annotation
)提供了一个接口。 有关更多详细信息,请参阅 Universe 上的本指南部分或包 scala.reflect.api
中的Universe API文档。
要使用 Scala 反射的大部份内容(包括本指南中提供的大多数代码示例),须要确保导入 Universe
或 Universe
成员。 一般,要使用运行时反射,可使用通配符导入来导入 scala.reflect.runtime.universe
的全部成员:
import scala.reflect.runtime.universe._
Mirror
是 Scala Reflection 的核心部分。经过反射提供的全部信息均可以经过这些所谓的 mirror 访问。根据要获取的信息类型或要采起的反射行为,必须使用不一样的 mirror 风格。
有关更多详细信息,请参阅本指南有关 mirror 的部分或包 scala.reflect.api
中的 Mirror API文档。