咱们知道,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
,它表示存在一个从T
到TypeTag[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
对象获取相关信息,必须借助Mirror
,Mirror
是按层级划分的,有ClassLoaderMirror
, ClassMirror
, InstanceMirror
, ModuleMirror
, MethodMirror
, FieldMirror
。经过ClassLoaderMirror
能够建立ClassMirror
, InstanceMirror
, ModuleMirror
, MethodMirror
, FieldMirror
。经过ClassMirror
, InstanceMirror
能够建立MethodMirror
, FieldMirror
。ModuleMirror
用于处理单例对象,一般是由object
定义的。从上述代码中能够发现,首先获取一个ClassLoaderMirror
,而后经过该Mirror
建立一个ClassMirror
,继续建立MethodMirror
,经过该MethodMirror
调用构造函数。从一个Mirror
建立另外一个Mirror
,须要指定一个Symbol
,Symbol
其实就是绑定名字和一个实体,有ClassSymbol
、 MethodSymbol
、 FieldSymbol
等,Symbol
的获取是经过Type
对象方法去查询,例如上述代码中经过declaration
方法查询构造函数的Symbol
。code
下面举例阐述访问运行时类成员,同理,咱们只需逐步建立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
,经过FieldMirror
的get
和set
方法去访问和修改为员变量。
说了这么多,貌似利用java的反射机制也能够实现上述功能,还不用这么的费劲。关键仍是在于你要访问编译器擦除后的类型信息仍是擦除前的类型信息,若是是访问擦除后的类型信息,使用java和scala的反射均可以,可是访问擦除前的类型信息,那就必需要使用scala的反射,由于java的反射并不知道擦除前的信息。
举个栗子,一步步剖析,首先定义一个基类A
,它包含一个抽象类型成员T
,而后分别派生出两个子类B
和C
。
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
如今分别实例化B
和C
的一个对象,并将抽象类型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的反射机制判断对象b
和c
的运行时类型是不是父子关系。
scala> b.getClass.isAssignableFrom(c.getClass) res3: Boolean = false
能够看到经过java的反射判断对象c
的运行时类型并非对象b
的运行时类型的子类。然而从咱们的定义来看,对象c
的类型本应该是对象b
的类型的子类,到这里咱们就会想到编译器,在实例化对象b
和c
时其实是经过匿名类来实例化的,一开始定义类型信息在编译的时候被擦除了,转为匿名类了。下面经过scala的反射机制判断对象b
和c
的运行时类型是不是父子关系。
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反射(一)