此文件頁面特定於 Scala 2 中發布的功能,這些功能已在 Scala 3 中移除或由替代方案取代。除非另有說明,此頁面中的所有程式碼範例都假設您使用 Scala 2。
實驗性
註解
在 Scala 中,可以使用 scala.annotation.Annotation
的子類別註解宣告。此外,由於 Scala 與 Java 的註解系統 整合,因此可以處理標準 Java 編譯器產生的註解。
如果對應的註解已持久化,則可以反思檢視註解,以便從包含註解宣告的類別檔中讀取。可透過繼承 scala.annotation.StaticAnnotation
或 scala.annotation.ClassfileAnnotation
,讓自訂註解類型持久化。因此,註解類型的執行個體會儲存在對應類別檔中的特殊屬性中。請注意,僅子類化 scala.annotation.Annotation
不足以讓對應的元資料持久化以供執行時期反思。此外,子類化 scala.annotation.ClassfileAnnotation
並不會讓您的註解在執行時期顯示為 Java 註解;這需要使用 Java 編寫註解類別。
API 區分兩種註解
- Java 註解:Java 編譯器產生的定義上的註解,即附加到程式定義上的
java.lang.annotation.Annotation
子類型。Scala 反思讀取時,scala.annotation.ClassfileAnnotation
特質會自動新增為每個 Java 註解的子類別。 - Scala 註解:Scala 編譯器產生的定義或類型上的註解。
Java 和 Scala 注解之間的區別在 scala.reflect.api.Annotations#Annotation
合約中體現,它同時公開了 scalaArgs
和 javaArgs
。對於擴充 scala.annotation.ClassfileAnnotation
的 Scala 或 Java 注解,scalaArgs
為空,且參數(如果有的話)儲存在 javaArgs
中。對於所有其他 Scala 注解,參數儲存在 scalaArgs
中,且 javaArgs
為空。
在 scalaArgs
中的參數表示為型別樹。請注意,這些樹不會被型別檢查器之後的任何階段轉換。在 javaArgs
中的參數表示為從 scala.reflect.api.Names#Name
到 scala.reflect.api.Annotations#JavaArgument
的映射。JavaArgument
的實例表示不同類型的 Java 注解參數
- 字面值(原始值和字串常數)、
- 陣列,以及
- 巢狀註解。
名稱
名稱是字串的簡單包裝器。 Name 有兩個子類型 TermName
和 TypeName
,用來區分項(例如物件或成員)和類型(例如類別、特質和類型成員)的名稱。名稱相同的項和類型可以在同一個物件中並存。換句話說,類型和項有各自的命名空間。
名稱與一個宇宙相關。範例
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> val mapName = TermName("map")
mapName: scala.reflect.runtime.universe.TermName = map
在上面,我們建立一個與執行時期反射宇宙相關的 Name
(這也可以在它的路徑依賴類型 reflect.runtime.universe.TermName
中看到)。
名稱通常用於查詢類型的成員。例如,要搜尋 List
類別中宣告的 map
方法(它是一個項),可以執行
scala> val listTpe = typeOf[List[Int]]
listTpe: scala.reflect.runtime.universe.Type = scala.List[Int]
scala> listTpe.member(mapName)
res1: scala.reflect.runtime.universe.Symbol = method map
要搜尋類型成員,可以遵循相同的程序,改用 TypeName
。
標準名稱
某些名稱,例如「_root_
」,在 Scala 程式中具有特殊意義。因此,它們對於反射性地存取某些 Scala 建構至關重要。例如,反射性地呼叫建構函式需要使用標準名稱 universe.termNames.CONSTRUCTOR
,這是 JVM 上表示建構函式名稱的項名稱 <init>
。
同時有
- 標準項名稱,例如「
<init>
」、「package
」和「_root_
」,以及 - 標準類型名稱,例如「
<error>
」、「_
」和「_*
」。
有些名稱,例如「package」,同時存在類型名稱和項名稱。標準名稱可透過類別 Universe
的 termNames
和 typeNames
成員取得。如需所有標準名稱的完整說明,請參閱 API 文件。
範圍
範圍物件通常將名稱對應到對應詞彙範圍中可用的符號。範圍可以巢狀。不過,反射 API 中公開的基本類型只公開一個最小介面,將範圍表示為 Symbol 的可迭代物件。
成員範圍 中公開了附加功能,這些範圍由 scala.reflect.api.Types#TypeApi 中定義的 members
和 decls
傳回。scala.reflect.api.Scopes#MemberScope 支援 sorted
方法,此方法會依宣告順序對成員進行排序。
下列範例會傳回 List
類別所有 final 成員的符號清單,並依宣告順序排列
scala> val finals = listTpe.decls.sorted.filter(_.isFinal)
finals: List(method isEmpty, method map, method collect, method flatMap, method takeWhile, method span, method foreach, method reverse, method foldRight, method length, method lengthCompare, method forall, method exists, method contains, method find, method mapConserve, method toList)
Expr
除了抽象語法樹的基本類型 scala.reflect.api.Trees#Tree
之外,已類型化的樹也可以表示為類型 scala.reflect.api.Exprs#Expr
的執行個體。Expr
會包裝一個抽象語法樹和一個內部類型標籤,以提供對樹類型的存取權。 Expr
主要用於簡單且方便地建立已類型化的抽象語法樹,以便在巨集中使用。在大部分情況下,這會涉及 reify
和 splice
方法(請參閱 巨集指南 以取得詳細資料)。
旗標和旗標集
旗標用來提供抽象語法樹的修改項,這些語法樹透過 scala.reflect.api.Trees#Modifiers
的 flags
欄位來表示定義。接受修改項的語法樹為
scala.reflect.api.Trees#ClassDef
。類別和特質。scala.reflect.api.Trees#ModuleDef
。物件。scala.reflect.api.Trees#ValDef
。值、變數、參數和自我類型註解。scala.reflect.api.Trees#DefDef
。方法和建構函式。scala.reflect.api.Trees#TypeDef
。類型別名、抽象類型成員和類型參數。
例如,要建立一個名為 C
的類別,可以寫類似以下的程式碼
ClassDef(Modifiers(NoFlags), TypeName("C"), Nil, ...)
在此,旗標集是空的。要讓 C
為私有的,可以寫類似以下的程式碼
ClassDef(Modifiers(PRIVATE), TypeName("C"), Nil, ...)
旗標也可以與垂直線運算子 (|
) 結合使用。例如,一個私有的 final 類別可以寫成類似以下的程式碼
ClassDef(Modifiers(PRIVATE | FINAL), TypeName("C"), Nil, ...)
所有可用的旗標清單定義在 scala.reflect.api.FlagSets#FlagValues
中,可透過 scala.reflect.api.FlagSets#Flag
取得。(通常會針對此部分使用萬用字元匯入,例如 import scala.reflect.runtime.universe.Flag._
。)
定義樹會編譯成符號,因此這些樹的修改項上的旗標會轉換成結果符號上的旗標。與樹不同的是,符號不會顯示旗標,而是提供遵循 isXXX
模式的測試方法(例如,可以使用 isFinal
來測試最終性)。在某些情況下,這些測試方法需要使用 asTerm
、asType
或 asClass
進行轉換,因為某些旗標只對特定類型的符號有意義。
請注意:鏡像 API 的此部分正被視為重新設計的候選對象。在鏡像 API 的未來版本中,旗標組很可能會被其他東西取代。
常數
Scala 規範中稱為常數表達式的特定表達式可以在編譯時間由 Scala 編譯器評估。下列類型的表達式是編譯時間常數(請參閱Scala 語言規範的第 6.24 節)
-
原始值類別的文字(Byte、Short、Int、Long、Float、Double、Char、Boolean 和 Unit) - 直接表示為對應的類型。
-
字串文字 - 表示為字串的實例。
-
類別的參考,通常使用 scala.Predef#classOf 建構 - 表示為類型。
-
Java 列舉值的參考 - 表示為符號。
常數表達式用於表示
- 抽象語法樹中的文字(請參閱
scala.reflect.api.Trees#Literal
),以及 - Java 類別檔案註解的文字引數(請參閱
scala.reflect.api.Annotations#LiteralArgument
)。
範例
scala> Literal(Constant(5))
val res6: reflect.runtime.universe.Literal = 5
上述表達式會建立一個 AST,表示 Scala 原始碼中的整數文字 5
。
Constant
是「虛擬案例類別」的一個範例,即,一個類別,其實例可以建構並比對,就像它是案例類別一樣。類型 Literal
和 LiteralArgument
都有一個 value
方法,用於傳回文字底層的編譯時間常數。
範例
Constant(true) match {
case Constant(s: String) => println("A string: " + s)
case Constant(b: Boolean) => println("A Boolean value: " + b)
case Constant(x) => println("Something else: " + x)
}
assert(Constant(true).value == true)
類別參考表示為 scala.reflect.api.Types#Type
的執行個體。此類參考可以使用 RuntimeMirror
的 runtimeClass
方法轉換為執行時期類別,例如 scala.reflect.runtime.currentMirror
。(必須從類型轉換為執行時期類別,因為當 Scala 編譯器處理類別參考時,底層執行時期類別可能尚未編譯。)
Java 列舉值參考表示為符號(scala.reflect.api.Symbols#Symbol
的執行個體),在 JVM 上指向傳回底層列舉值的函式。可以使用 RuntimeMirror
來檢查底層列舉或取得對列舉的參考的執行時期值。
範例
// Java source:
enum JavaSimpleEnumeration { FOO, BAR }
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface JavaSimpleAnnotation {
Class<?> classRef();
JavaSimpleEnumeration enumRef();
}
@JavaSimpleAnnotation(
classRef = JavaAnnottee.class,
enumRef = JavaSimpleEnumeration.BAR
)
public class JavaAnnottee {}
// Scala source:
import scala.reflect.runtime.universe._
import scala.reflect.runtime.{currentMirror => cm}
object Test extends App {
val jann = typeOf[JavaAnnottee].typeSymbol.annotations(0).javaArgs
def jarg(name: String) = jann(TermName(name)) match {
// Constant is always wrapped in a Literal or LiteralArgument tree node
case LiteralArgument(ct: Constant) => value
case _ => sys.error("Not a constant")
}
val classRef = jarg("classRef").value.asInstanceOf[Type]
println(showRaw(classRef)) // TypeRef(ThisType(), JavaAnnottee, List())
println(cm.runtimeClass(classRef)) // class JavaAnnottee
val enumRef = jarg("enumRef").value.asInstanceOf[Symbol]
println(enumRef) // value BAR
val siblings = enumRef.owner.typeSignature.decls
val enumValues = siblings.filter(sym => sym.isVal && sym.isPublic)
println(enumValues) // Scope {
// final val FOO: JavaSimpleEnumeration;
// final val BAR: JavaSimpleEnumeration
// }
val enumClass = cm.runtimeClass(enumRef.owner.asClass)
val enumValue = enumClass.getDeclaredField(enumRef.name.toString).get(null)
println(enumValue) // BAR
}
印表機
列印樹狀結構
方法 show
顯示反射成品的「美化」表示法。此表示法提供 Scala 程式碼的去糖 Java 表示法。例如
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> def tree = reify { final class C { def x = 2 } }.tree
tree: scala.reflect.runtime.universe.Tree
scala> show(tree)
res0: String =
{
final class C extends AnyRef {
def <init>() = {
super.<init>();
()
};
def x = 2
};
()
}
方法 showRaw
顯示給定反射物件的內部結構,為 Scala 抽象語法樹 (AST),這是 Scala 型別檢查器運作的表示法。
請注意,儘管此表示形式似乎會產生正確的樹狀結構,您可能會認為可以在巨集實作中使用,但通常並非如此。符號並未完全表示(僅表示其名稱)。因此,此方法最適合用於在給定一些有效的 Scala 程式碼的情況下單純檢查 AST。
scala> showRaw(tree)
res1: String = Block(List(
ClassDef(Modifiers(FINAL), TypeName("C"), List(), Template(
List(Ident(TypeName("AnyRef"))),
emptyValDef,
List(
DefDef(Modifiers(), termNames.CONSTRUCTOR, List(), List(List()), TypeTree(),
Block(List(
Apply(Select(Super(This(typeNames.EMPTY), typeNames.EMPTY), termNames.CONSTRUCTOR), List())),
Literal(Constant(())))),
DefDef(Modifiers(), TermName("x"), List(), List(), TypeTree(),
Literal(Constant(2))))))),
Literal(Constant(())))
方法 showRaw
也可以列印 scala.reflect.api.Types
,並放在要檢查的工件旁邊。
scala> import scala.tools.reflect.ToolBox // requires scala-compiler.jar
import scala.tools.reflect.ToolBox
scala> import scala.reflect.runtime.{currentMirror => cm}
import scala.reflect.runtime.{currentMirror=>cm}
scala> showRaw(cm.mkToolBox().typeCheck(tree), printTypes = true)
res2: String = Block[1](List(
ClassDef[2](Modifiers(FINAL), TypeName("C"), List(), Template[3](
List(Ident[4](TypeName("AnyRef"))),
emptyValDef,
List(
DefDef[2](Modifiers(), termNames.CONSTRUCTOR, List(), List(List()), TypeTree[3](),
Block[1](List(
Apply[4](Select[5](Super[6](This[3](TypeName("C")), typeNames.EMPTY), ...))),
Literal[1](Constant(())))),
DefDef[2](Modifiers(), TermName("x"), List(), List(), TypeTree[7](),
Literal[8](Constant(2))))))),
Literal[1](Constant(())))
[1] TypeRef(ThisType(scala), scala.Unit, List())
[2] NoType
[3] TypeRef(NoPrefix, TypeName("C"), List())
[4] TypeRef(ThisType(java.lang), java.lang.Object, List())
[5] MethodType(List(), TypeRef(ThisType(java.lang), java.lang.Object, List()))
[6] SuperType(ThisType(TypeName("C")), TypeRef(... java.lang.Object ...))
[7] TypeRef(ThisType(scala), scala.Int, List())
[8] ConstantType(Constant(2))
列印類型
方法 show
可用於產生類型的可讀取字串表示形式
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> def tpe = typeOf[{ def x: Int; val y: List[Int] }]
tpe: scala.reflect.runtime.universe.Type
scala> show(tpe)
res0: String = scala.AnyRef{def x: Int; val y: scala.List[Int]}
與 scala.reflect.api.Trees
的方法 showRaw
類似,scala.reflect.api.Types
的 showRaw
提供了 Scala AST 的視覺化,由 Scala 型別檢查器操作。
scala> showRaw(tpe)
res1: String = RefinedType(
List(TypeRef(ThisType(scala), TypeName("AnyRef"), List())),
Scope(
TermName("x"),
TermName("y")))
showRaw
方法也有命名參數 printIds
和 printKinds
,兩者的預設引數皆為 false
。當將 true
傳遞給這些參數時,showRaw
會另外顯示符號的唯一識別碼,以及它們的種類(套件、類型、方法、取得器等)。
scala> showRaw(tpe, printIds = true, printKinds = true)
res2: String = RefinedType(
List(TypeRef(ThisType(scala#2043#PK), TypeName("AnyRef")#691#TPE, List())),
Scope(
TermName("x")#2540#METH,
TermName("y")#2541#GET))
位置
位置(Position 特質的實例)用於追蹤符號和樹狀節點的來源。它們通常用於顯示警告和錯誤,以指出程式中不正確的點。位置會指出原始檔中的欄和行(從原始檔開頭的偏移量稱為其「點」,有時使用起來較不方便)。它們也會載入它們所參考的行內容。並非所有樹狀結構或符號都有位置;使用 NoPosition
物件指出遺失的位置。
位置可以只參考原始檔中的單一字元,或參考一個範圍。在後者的情況下,會使用範圍位置(不是範圍位置的位置也稱為偏移位置)。範圍位置額外有 start
和 end
偏移量。可以使用 focusStart
和 focusEnd
方法「聚焦」於 start
和 end
偏移量,這些方法會傳回位置(當呼叫在非範圍位置的位置上時,它們只會傳回 this
)。
可以使用 precedes
等方法來比較位置,如果兩個位置都已定義(即,位置不是 NoPosition
),且 this
位置的終點不超過給定位置的起點,則此方法會成立。此外,可以測試範圍位置是否包含(使用 includes
方法)和重疊(使用 overlaps
方法)。
範圍位置不是透明就是不透明(非透明)。範圍位置是否不透明會影響其允許的使用方式,因為包含範圍位置的樹狀結構必須符合下列不變式
- 具有偏移位置的樹狀結構絕不會包含具有範圍位置的子項
- 如果具有範圍位置的樹狀結構的子項也具有範圍位置,則子項的範圍會包含在父項的範圍內。
- 同一個節點的子項的不透明範圍位置不會重疊(表示它們的重疊最多只有一個點)。
使用 makeTransparent
方法,可以將不透明範圍位置轉換為透明範圍位置;所有其他位置則維持不變。