【Scala反射】反射概述
概述Reflection 是一种程序检查,甚至可能是自我修改的能力。 它在面向对象、函数式和逻辑编程范例方面有着悠久的历史。虽然只有一些语言是以反射为指导原则,但随着时间的推移,许多语言逐渐发展出反射能力。 反射涉及到对程序的其他隐含元素进行具体化(即明确表达)的能力。 这些元素可以是静态程序元素,如类、方法或表达式,也可以是动态元素,如当前的延续或完成事件,如方法调用和字段访问。 通常从根据执行反射过程的时间区分编译时和运行时反射。 编译时反射是开发程序转换器和生成器的强大方式,而运行时反射通常用于调整语言语义或支持软件组件之间的后期绑定。 Scala直到2.10这个版本也还没有任何反射工具。而代替方式是,使用Java反射API的一部分,即处理提供动态检查类和对象并访问其成员的能力。然而,在独立Java反射下,许多Scala特定的元素是不可恢复的,它仅公开Java元素(没有函数,没有特质)和类型(没有存在判别、高阶特性、路径依赖和抽象类型)。另外,Java反射也无法恢复编译时通用的Java类型的运行时类型信息、通过运行时反射到Scala中的泛型类型的限制。 在Scala 2.10中,引入了一个新的反射库,不仅解决了Java在Scala特定类型和泛型类型上运行时反射的缺点,而且为Scala增加了一个更强大的通用反射功能工具箱。除了Scala类型和泛型的全功能运行时反射之外,Scala 2.10还提供了以宏的形式的编译时反射功能,以及将Scala表达式变为抽象语法树的功能。 运行时反射什么是运行时反射? 在运行时给定某个对象的类型或实例,反射就是能够:
让我们来看看如何通过几个例子来完成上述每一步。 检查运行时类型(包括运行时的通用类型)与其他JVM语言一样,Scala的类型在编译时被擦除。这意味着如果您要检查某个实例的运行时类型,则可能无法访问Scala编译器在编译时可用的所有类型信息。
例如,使用上下文边界: 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,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> 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 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) 第二步,通过使用 scala> val ctor = ru.typeOf[Person].decl(ru.termNames.CONSTRUCTOR).asMethod ctor: scala.reflect.runtime.universe.MethodSymbol = constructor Person
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,false) 在这个例子中,我们将尝试以反射的方式获取并设置 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 cala> val shippingTermSymb = ru.typeOf[Purchase].decl(ru.TermName("shipped")).asTerm shippingTermSymb: scala.reflect.runtime.universe.TermSymbol = method shipped 我们现在查看 scala> val im = m.reflect(p) im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for Purchase(Jeff Lebowski,false) scala> val shippingFieldMirror = im.reflectField(shippingTermSymb) shippingFieldMirror: scala.reflect.runtime.universe.FieldMirror = field mirror for Purchase.shipped (bound to Purchase(Jeff Lebowski,false)) 为了访问特定实例的 现在我们为特定领域提供了一个 scala> shippingFieldMirror.get res7: Any = false scala> shippingFieldMirror.set(true) scala> shippingFieldMirror.get res9: Any = true Java中的运行时类 vs. Scala中的运行时类型那些习惯使用Java反射在运行时获得Java类实例的人可能已经注意到,在Scala中,我们改为获得运行时类型。 下面的REPL运行显示了一个非常简单的场景,在Scala类上使用Java反射可能会返回令人惊讶或不正确的结果。 首先,我们用一个抽象类型成员 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 然后,我们对 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 的 scala> c.getClass.isAssignableFrom(d.getClass) res6: Boolean = false 在上面的代码中,我们看到 在这些情况下,我们可以使用 Scala 反射来获取这些 Scala 对象的精确运行时类型。Scala 运行时类型携带编译时的所有类型信息,避免编译时和运行时之间的这些类型不匹配。 下面,我们定义一个使用 Scala 反射来获取其参数的运行时类型的方法,然后检查两者之间的子类型关系。如果其第一个参数的类型是其第二个参数类型的子类型,则返回 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 正如我们所看到的,我们现在得到了预期的结果—— 编译时反射Scala反射启用了一种元编程形式,可以让程序在编译时自行修改。 这种编译时反射是以宏的形式实现的,它提供了在编译时执行操纵抽象语法树的方法的能力。 宏的一个特别有趣的方面是它们基于 Scala 的运行时反射所使用的相同的API,在包 请注意,宏指南侧重于宏特性,而本指南重点介绍反射API的一般方面。不过,许多概念直接应用于宏,例如抽象语法树,这些将在关于符号,树和类型的章节中详细讨论。 环境所有反射任务都需要建立适当的环境。这个环境根据反射任务是在运行时还是在编译时完成而不同。在运行时或编译时使用的环境之间的区别被封装在一个所谓的 universe 中。反射环境的另一个重要方面是我们可以反射访问的一组实体。这组实体由所谓的 mirror 确定。 mirror 不仅确定可反射访问的一组实体。 他们还提供对这些实体进行反射的操作。 例如,在运行时反射中,调用者 mirror 可用于调用类的方法或构造函数。 Universe
要使用 Scala 反射的大部分内容(包括本指南中提供的大多数代码示例),需要确保导入 import scala.reflect.runtime.universe._ Mirror
有关更多详细信息,请参阅本指南有关 mirror 的部分或包 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |