Scala反射(一)

本篇文章主要讓你們理解什麼是Scala的反射, 以及反射的分類, 反射的一些術語概念和一些簡單的反射例子.php

什麼是反射

咱們知道, Scala是基於JVM的語言, Scala編譯器會將Scala代碼編譯成JVM字節碼, 而JVM編譯過程當中會擦除一些泛型信息, 這就叫類型擦除(type-erasure ).html

而咱們開發過程當中, 可能須要在某一時刻得到類中的詳細泛型信息. 並進行邏輯處理. 這時就須要用的 這個概念. 經過反射咱們能夠作到java

  1. 獲取運行時類型信息
  2. 經過類型信息實例化新對象
  3. 訪問或調用對象的方法和屬性等

在Scala-2.10之前, 只能在Scala中利用Java的反射機制, 可是經過Java反射機制獲得的是隻是擦除後的類型信息, 並不包括Scala的一些特定類型信息. 從Scala-2.10起, Scala實現了本身的反射機制, 咱們能夠經過Scala的反射機制獲得Scala的類型信息。python

Scala 反射的分類

Scala 的反射分爲兩個範疇:es6

  • 運行時反射
  • 編譯時反射

這二者之間的區別在於Environment, 而Environment又是由universe決定的. 反射的另外一個重要的部分就是一個實體集合,而這個實體集合被稱爲mirror,有了這個實體集合咱們就能夠實現對須要反射的類進行對應的操做,如屬性的獲取,屬性值得設置,以及對反射類方法的調用(其實就是成員函數的入口地址, 但請注意, 這只是個地址)!編程

可能有點繞, 說的直白點就是要操做類方法或者屬性就須要得到指定的mirror,而mirror又是從Environment中得來的,而Environment又是Universes中引入的,而Universes根據運行時和編譯時又能夠分爲兩個領域的.api

對於不一樣的反射, 咱們須要引入不一樣的Universesruby

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 => .class 過程

總體流程以下

image

詞法分析

首先Scala編譯器要讀取源代碼, 一個字節一個字節地讀進來, 找到關鍵詞如ifforwhile等, 這就是詞法分析的過程. 這個過程結束之後.Scala代碼就變成了規範的Token流, 就像咱們把一句話中的名詞、動詞、標點符號分辨出來.

語法分析

接着Scala編譯器就是對Token流進行語法分析了, 好比if後面跟着的是否是一個布爾型的變量, 並將符合規範的語法使用語法樹存貯起來. 之因此要用語法樹來存儲, 是由於這樣作能夠方便之後對這棵樹按照新的規則從新組織, 這也是編譯器的關鍵所在.

語義分析

以後Scala編譯器會進行語義分析. 由於能夠保證造成語法樹之後不存在語法錯誤, 但語義是否正確則沒法保證. 還有就是Scala會有一些相對複雜的語法, 語義分析器的做用就是將這些複雜的語法翻譯成更簡單的語法, 好比將foreach翻譯成簡單的for循環, 使它更接近目標語言的語法規則.

字節碼生成

最後就是由代碼生成器將將語義分析的結果生成符合JVM規範的字節碼了.

符號 Symbol

Symbol Table, 簡單的來講是用於編譯器或者解釋器的一種數據結構, 一般是用HashTable實現. 它所記載的信息一般是標識符(identifier)的相關信息,如類型,做用域等。那麼,它一般會在語義分析(Semantic Analysis)階段運用到.

抽象語法樹 AST

語法樹經過樹結構來描述開始符到產生式的推導過程.

在計算機科學中,抽象語法樹(abstract syntax tree 或者縮寫爲 AST),或者語法樹(syntax tree),是源代碼的抽象語法結構的樹狀表現形式,這裏特指編程語言的源代碼。樹上的每一個節點都表示源代碼中的一種結構。之因此說語法是「抽象」的,是由於這裏的語法並不會表示出真實語法中出現的每一個細節。

運行時反射

Scala運行時類型信息是保存在TypeTag對象中, 編譯器在編譯過程當中將類型信息保存到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

推薦閱讀: Scala類型系統之:來自星星的貓

邊界

瞭解了泛型, 下面咱們說一下上下文界定[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]的隱式轉換.

typeTag 總結

如今咱們在回頭看一下最初的例子:

scala> def getTypeTag[T : ru.TypeTag](obj: T) = ru.typeOf[T]

這裏, 咱們把Fruits 換成了 ru.TypeTag, 可是咱們並無看到有T => TypeTag[T]的隱式轉換啊!

不要急, 聽我說, 由於前面已經講過了, Scala運行時類型信息是保存在TypeTag對象中, 編譯器在編譯過程當中將類型信息保存到TypeTag中, 並將其攜帶到運行期. 也就是說, Scala編譯器會自動爲咱們生成一個隱式, 只要咱們在方法中定義了這個邊界.

ClassTag

一旦咱們獲取到了類型信息(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

Mirror是按層級劃分的,有

  • ClassLoaderMirror
    • ClassMirror ( => 類)
      • MethodMirror ( => 方法)
      • FieldMirror ( => 成員)
    • InstanceMirror ( => 實例)
      • MethodMirror
      • FieldMirror
    • ModuleMirror ( => Object)
    • MethodMirror
    • FieldMirror

運行時類成員訪問

話很少說, 舉例來看:

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

編譯時反射

 請參考:

瞭解Scala反射(二)

瞭解Scala 宏

相關文章
相關標籤/搜索