Scala反射(二)

咱們知道,scala編譯器會將scala代碼編譯成JVM字節碼,編譯過程當中會擦除scala特有的一些類型信息,在scala-2.10之前,只能在scala中利用java的反射機制,可是經過java反射機制獲得的是隻是擦除後的類型信息,並不包括scala的一些特定類型信息。從scala-2.10起,scala實現了本身的反射機制,咱們能夠經過scala的反射機制獲得scala的類型信息。scala反射包括運行時反射和編譯時反射,本文主要闡述運行時反射的一些用法,方便scala開發人員參考,具體原理細節請查看官方文檔。本文涉及到的代碼示例是基於scala-2.10.4,若有不一樣請勿對號入座。html

給定類型或者對象實例,經過scala運行時反射,能夠作到:1)獲取運行時類型信息;2)經過類型信息實例化新對象;3)訪問或調用對象的方法和屬性等。下面分別舉例闡述運行時反射的功能。java

獲取運行時類型信息

scala運行時類型信息是保存在TypeTag對象中,編譯器在編譯過程當中將類型信息保存到TypeTag中,並將其攜帶到運行期。咱們能夠經過typeTag方法獲取TypeTag類型信息。api

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 REPL顯示,經過typeTag方法獲取List[Int]類型的TypeTag對象,該對象包含了List[Int]的詳細類型信息,經過TypeTag對象的tpe方法獲得由Type對象封裝具體的類型信息,能夠看到該Type對象的類型信息精確到了類型參數Int。若是僅僅是獲取類型信息,還有一個更簡便的方法,那就是經過typeOf方法。ide

scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._

scala> typeOf[List[Int]]
res0: reflect.runtime.universe.Type = scala.List[Int]

這時有人就會問,typeTag方法須要傳一個具體的類型,事先知道類型還要TypeTag有啥用啊。咱們不妨寫個方法,獲取任意對象的類型信息。函數

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.typeTag[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).tpe
theType: ru.Type = List[Int]

如上scala REPL顯示,方法getTypeTag能夠獲取任意對象的類型信息,注意方法中的上下文界定T: ru.TypeTag ,它表示存在一個從TTypeTag[T]的隱式轉換,前面已經講到,TypeTag對象是在編譯期間由編譯器生成的,若是不加這個上下文界定,編譯器就不會爲T生成TypeTag對象。固然也能夠經過隱式參數替代上下文界定,就如同REPL中顯示的implicit evidence$1: ru.TypeTag[T]那樣。一旦咱們獲取到了類型信息(Type instance),咱們就能夠經過該Type對象查詢更詳盡的類型信息。post

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 ++)

到這裏咱們知道TypeTag對象封裝了Type對象,經過Type對象能夠獲取詳盡的類型信息,包括方法和屬性等,經過typeTag方法能夠獲得TypeTag對象,經過typeOf方法能夠獲得Type對象,它包含沒有被編譯器擦除的含完整的scala類型信息,與之對應地,若是想獲取擦除後的類型信息,傳統的方法能夠經過java的反射機制來實現,可是scala也提供了該功能,經過classTag方法能夠獲取ClassTag對象,ClassTag封裝了擦除後的類型信息,經過classOf方法能夠獲取Class對象,這與java反射中的Class對象一致。url

scala> import scala.reflect._
import scala.reflect._

scala> val clsTag = classTag[List[Int]]
clsTag: scala.reflect.ClassTag[List[Int]] = scala.collection.immutable.List

scala> clsTag.runtimeClass
res0: Class[_] = class scala.collection.immutable.List

scala> val cls = classOf[List[Int]]
cls: Class[List[Int]] = class scala.collection.immutable.List

scala> cls.[tab鍵補全]
asInstanceOf              asSubclass          cast
desiredAssertionStatus    getAnnotation
getAnnotations            getCanonicalName    ...

從上述scala REPL中能夠看到,ClassTag對象包含了Class對象,經過Class對象僅僅能夠獲取擦除後的類型信息,經過在scala REPL中用tab補全能夠看到經過Class對象能夠獲取的信息。es5

運行時類型實例化

咱們已經知道經過Type對象能夠獲取未擦除的詳盡的類型信息,下面咱們經過Type對象中的信息找到構造方法並實例化類型的一個對象。scala

scala> case class Person(id: Int, name: String)
defined class Person

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

scala> val m = ru.runtimeMirror(getClass.getClassLoader)
m: ru.Mirror = JavaMirror with scala.tools.nsc.interpreter.IMain$TranslatingClassLoader@a57fc5f ...

scala> val classPerson = ru.typeOf[Person].typeSymbol.asClass
classPerson: ru.ClassSymbol = class Person

scala> val cm = m.reflectClass(classPerson)
cm: ru.ClassMirror = class mirror for Person (bound to null)

scala> val ctor = ru.typeOf[Person].declaration(ru.nme.CONSTRUCTOR).asMethod
ctor: ru.MethodSymbol = constructor Person

scala> val ctorm = cm.reflectConstructor(ctor)
ctorm: ru.MethodMirror = constructor mirror for Person.<init>(id: scala.Int, name: String): Person (bound to null)

scala> val p = ctorm(1, "Mike")
p: Any = Person(1,Mike)

如上scala REPL代碼,要想經過Type對象獲取相關信息,必須藉助MirrorMirror是按層級劃分的,有ClassLoaderMirrorClassMirrorInstanceMirrorModuleMirrorMethodMirrorFieldMirror。經過ClassLoaderMirror能夠建立ClassMirrorInstanceMirrorModuleMirrorMethodMirrorFieldMirror。經過ClassMirrorInstanceMirror能夠建立MethodMirrorFieldMirrorModuleMirror用於處理單例對象,一般是由object定義的。從上述代碼中能夠發現,首先獲取一個ClassLoaderMirror,而後經過該Mirror建立一個ClassMirror,繼續建立MethodMirror,經過該MethodMirror調用構造函數。從一個Mirror建立另外一個Mirror,須要指定一個SymbolSymbol其實就是綁定名字和一個實體,有ClassSymbol、 MethodSymbol、 FieldSymbol等,Symbol的獲取是經過Type對象方法去查詢,例如上述代碼中經過declaration方法查詢構造函數的Symbolcode

運行時類成員訪問

下面舉例闡述訪問運行時類成員,同理,咱們只需逐步建立FieldMirror來訪問類成員。

scala> case class Person(id: Int, name: String)
defined class Person

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

scala> val m = ru.runtimeMirror(getClass.getClassLoader)
m: ru.Mirror = JavaMirror with scala.tools.nsc.interpreter.IMain$TranslatingClassLoader@a57fc5f ...

scala> val p = Person(1, "Mike")
p: Person = Person(1,Mike)

scala> val nameTermSymb = ru.typeOf[Person].declaration(ru.newTermName("name")).asTerm
nameTermSymb: ru.TermSymbol = value name

scala> val im = m.reflect(p)
im: ru.InstanceMirror = instance mirror for Person(1,Mike)

scala> val nameFieldMirror = im.reflectField(nameTermSymb)
nameFieldMirror: ru.FieldMirror = field mirror for Person.name (bound to Person(1,Mike))

scala> nameFieldMirror.get
res0: Any = Mike

scala> nameFieldMirror.set("Jim")

scala> p.name
res2: String = Jim

如上代碼所示,經過層級ClassLoaderMirror->InstanceMirror->FieldMirror獲得FieldMirror,經過Type對象調用方法declaration(ru.newTermName("name"))獲取name字段的Symbol,經過FieldMirrorgetset方法去訪問和修改爲員變量。

說了這麼多,貌似利用java的反射機制也能夠實現上述功能,還不用這麼的費勁。關鍵仍是在於你要訪問編譯器擦除後的類型信息仍是擦除前的類型信息,若是是訪問擦除後的類型信息,使用java和scala的反射均可以,可是訪問擦除前的類型信息,那就必需要使用scala的反射,由於java的反射並不知道擦除前的信息。

舉個栗子,一步步剖析,首先定義一個基類A,它包含一個抽象類型成員T,而後分別派生出兩個子類BC

scala> class A {
     | type T
     | val x: Option[T] = None
     | }
defined class A

scala> class B extends A
defined class B

scala> class C extends B
defined class C

如今分別實例化BC的一個對象,並將抽象類型T具體化爲String類型。

scala> val b = new B { type T = String }
b: B{type T = String} = $anon$1@446344a8

scala> val c = new C { type T = String }
c: C{type T = String} = $anon$1@195bc0a4

如今經過java的反射機制判斷對象bc的運行時類型是不是父子關係。

scala> b.getClass.isAssignableFrom(c.getClass)
res3: Boolean = false

能夠看到經過java的反射判斷對象c的運行時類型並非對象b的運行時類型的子類。然而從咱們的定義來看,對象c的類型本應該是對象b的類型的子類,到這裏咱們就會想到編譯器,在實例化對象bc時其實是經過匿名類來實例化的,一開始定義類型信息在編譯的時候被擦除了,轉爲匿名類了。下面經過scala的反射機制判斷對象bc的運行時類型是不是父子關係。

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

scala> def isSubClass[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
     | }
isSubClass: [T, S](x: T, y: S)(implicit evidence$1: ru.TypeTag[T], implicit evidence$2: ru.TypeTag[S])Boolean

scala> isSubClass(c, b)
res5: Boolean = true

從上述代碼中能夠看到,經過scala的反射獲得的類型信息符合咱們一開始定義。因此在scala中最好是使用scala的反射而不要使用java的反射,由於頗有可能編譯後經過java的反射獲得的結果並非想象的那樣。

另參考:瞭解Scala反射(一)

相關文章
相關標籤/搜索