本篇文章主要讓你們理解什麼是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
請參考: