反射

環境、宇宙和鏡像

語言
此文件頁面專門針對 Scala 2 中提供的功能,這些功能已在 Scala 3 中移除或由其他功能取代。除非另有說明,此頁面中的所有程式碼範例都假設您使用 Scala 2。

實驗性

環境

反射環境會根據反射工作是在執行階段還是編譯階段而有所不同。在執行階段或編譯階段使用的環境之間的區別封裝在所謂的宇宙中。反射環境的另一個重要面向是我們具有反射存取權的實體集合。此實體集合由所謂的鏡像決定。

例如,可透過執行時間反射存取的實體是由 ClassloaderMirror 提供的。此鏡像僅提供特定類別載入器載入的實體(套件、類型和成員)的存取權。

鏡像不僅決定可透過反射存取的實體組。它們還提供要在這些實體上執行的反射操作。例如,在執行時間反射中,呼叫鏡像可用於呼叫類別的方法或建構函式。

宇宙

有兩種主要的宇宙類型,由於同時存在執行時間和編譯時間反射功能,因此必須使用與手邊任務相應的宇宙。下列任一宇宙:

  • scala.reflect.runtime.universe 適用於執行時間反射,或
  • scala.reflect.macros.Universe 適用於編譯時間反射

宇宙提供介面存取反射中使用所有主要概念,例如 TypesTreesAnnotations

鏡像

反射提供的所有資訊都是透過鏡像存取的。根據要取得的資訊類型或要執行的反射動作,必須使用不同類型的鏡像。類別載入器鏡像可用於取得類型和成員的表示。從類別載入器鏡像,可以取得更專業的呼叫鏡像(最常用的鏡像),實作反射呼叫,例如方法或建構函式呼叫和欄位存取。

摘要

  • 「類別載入器」鏡像。這些鏡像將名稱轉換為符號(透過方法 staticClass/staticModule/staticPackage)。

  • 「呼叫」鏡像。這些鏡像實作反射呼叫(透過方法 MethodMirror.applyFieldMirror.get 等)。這些「呼叫」鏡像是最常用的鏡像類型。

執行時間鏡像

在執行階段使用鏡像的進入點是透過 ru.runtimeMirror(<classloader>),其中 ruscala.reflect.runtime.universe

scala.reflect.api.JavaMirrors#runtimeMirror 呼叫的結果是類別載入器鏡像,類型為 scala.reflect.api.Mirrors#ReflectiveMirror,它可以載入符號名稱。

類別載入器鏡像可以建立呼叫器鏡像(包括 scala.reflect.api.Mirrors#InstanceMirrorscala.reflect.api.Mirrors#MethodMirrorscala.reflect.api.Mirrors#FieldMirrorscala.reflect.api.Mirrors#ClassMirrorscala.reflect.api.Mirrors#ModuleMirror)。

以下提供這兩種鏡像互動方式的範例。

鏡像類型、其使用案例和範例

ReflectiveMirror 用於載入符號名稱,並作為呼叫器鏡像的進入點。進入點:val m = ru.runtimeMirror(<classloader>)。範例

scala> val ru = scala.reflect.runtime.universe
ru: scala.reflect.api.JavaUniverse = ...

scala> val m = ru.runtimeMirror(getClass.getClassLoader)
m: scala.reflect.runtime.universe.Mirror = JavaMirror ...

InstanceMirror 用於為方法和欄位建立呼叫器鏡像,以及內部類別和內部物件(模組)。進入點:val im = m.reflect(<value>)。範例

scala> class C { def x = 2 }
defined class C

scala> val im = m.reflect(new C)
im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for C@3442299e

MethodMirror 用於呼叫執行個體方法(Scala 只有執行個體方法 - 物件的方法是物件執行個體的執行個體方法,可透過 ModuleMirror.instance 取得)。進入點:val mm = im.reflectMethod(<method symbol>)。範例

scala> val methodX = ru.typeOf[C].decl(ru.TermName("x")).asMethod
methodX: scala.reflect.runtime.universe.MethodSymbol = method x

scala> val mm = im.reflectMethod(methodX)
mm: scala.reflect.runtime.universe.MethodMirror = method mirror for C.x: scala.Int (bound to C@3442299e)

scala> mm()
res0: Any = 2

FieldMirror 用於取得/設定執行個體欄位(像方法一樣,Scala 只有執行個體欄位,請參閱上方)。進入點:val fm = im.reflectField(<field or accessor symbol>)。範例

scala> class C { val x = 2; var y = 3 }
defined class C

scala> val m = ru.runtimeMirror(getClass.getClassLoader)
m: scala.reflect.runtime.universe.Mirror = JavaMirror ...

scala> val im = m.reflect(new C)
im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for C@5f0c8ac1

scala> val fieldX = ru.typeOf[C].decl(ru.TermName("x")).asTerm.accessed.asTerm
fieldX: scala.reflect.runtime.universe.TermSymbol = value x

scala> val fmX = im.reflectField(fieldX)
fmX: scala.reflect.runtime.universe.FieldMirror = field mirror for C.x (bound to C@5f0c8ac1)

scala> fmX.get
res0: Any = 2

scala> fmX.set(3)

scala> val fieldY = ru.typeOf[C].decl(ru.TermName("y")).asTerm.accessed.asTerm
fieldY: scala.reflect.runtime.universe.TermSymbol = variable y

scala> val fmY = im.reflectField(fieldY)
fmY: scala.reflect.runtime.universe.FieldMirror = field mirror for C.y (bound to C@5f0c8ac1)

scala> fmY.get
res1: Any = 3

scala> fmY.set(4)

scala> fmY.get
res2: Any = 4

ClassMirror 來建立建構式的呼叫鏡像。進入點:對於靜態類別 val cm1 = m.reflectClass(<class symbol>),對於內部類別 val mm2 = im.reflectClass(<class symbol>)。範例

scala> case class C(x: Int)
defined class C

scala> val m = ru.runtimeMirror(getClass.getClassLoader)
m: scala.reflect.runtime.universe.Mirror = JavaMirror ...

scala> val classC = ru.typeOf[C].typeSymbol.asClass
classC: scala.reflect.runtime.universe.Symbol = class C

scala> val cm = m.reflectClass(classC)
cm: scala.reflect.runtime.universe.ClassMirror = class mirror for C (bound to null)

scala> val ctorC = ru.typeOf[C].decl(ru.termNames.CONSTRUCTOR).asMethod
ctorC: scala.reflect.runtime.universe.MethodSymbol = constructor C

scala> val ctorm = cm.reflectConstructor(ctorC)
ctorm: scala.reflect.runtime.universe.MethodMirror = constructor mirror for C.<init>(x: scala.Int): C (bound to null)

scala> ctorm(2)
res0: Any = C(2)

ModuleMirror 來存取單例物件的執行個體。進入點:對於靜態物件 val mm1 = m.reflectModule(<module symbol>),對於內部物件 val mm2 = im.reflectModule(<module symbol>)。範例

scala> object C { def x = 2 }
defined module C

scala> val m = ru.runtimeMirror(getClass.getClassLoader)
m: scala.reflect.runtime.universe.Mirror = JavaMirror ...

scala> val objectC = ru.typeOf[C.type].termSymbol.asModule
objectC: scala.reflect.runtime.universe.ModuleSymbol = object C

scala> val mm = m.reflectModule(objectC)
mm: scala.reflect.runtime.universe.ModuleMirror = module mirror for C (bound to null)

scala> val obj = mm.instance
obj: Any = C$@1005ec04

編譯時期鏡像

編譯時期鏡像僅使用類別載入器鏡像,以載入符號名稱。

進入類別載入器鏡像的進入點是透過 scala.reflect.macros.Context#mirror。使用類別載入器鏡像的典型方法包括 scala.reflect.api.Mirror#staticClassscala.reflect.api.Mirror#staticModulescala.reflect.api.Mirror#staticPackage。例如

import scala.reflect.macros.Context

case class Location(filename: String, line: Int, column: Int)

object Macros {
  def currentLocation: Location = macro impl

  def impl(c: Context): c.Expr[Location] = {
    import c.universe._
    val pos = c.macroApplication.pos
    val clsLocation = c.mirror.staticModule("Location") // get symbol of "Location" object
    c.Expr(Apply(Ident(clsLocation), List(Literal(Constant(pos.source.path)), Literal(Constant(pos.line)), Literal(Constant(pos.column)))))
  }
}

請注意:有幾個高級替代方案,可以用來避免手動查詢符號。例如,typeOf[Location.type].termSymbol(或 typeOf[Location].typeSymbol 如果我們需要 ClassSymbol),因為我們不必使用字串來查詢符號,所以這些是類型安全的。

此頁面的貢獻者