简体   繁体   中英

Scala accessing members of runtime types using reflection of a class that extends a trait

Suppose I have a MyItem trait and its companion object has an apply() function that creates a class instance called SubItem that extends from MyItem :

import scala.reflect.runtime.{universe => ru}

trait MyItem {
  import MyItem._
  def num: Option[Int]
}

object MyItem {
  class SubItem(val num: Option[Int]) extends MyItem 

  def apply(num: Option[Int]): MyItem = new SubItem(num) // creates SubItem
}

def getTypeTag[T: ru.TypeTag](obj: T) = ru.typeTag[T]

val modifiedItem = MyItem(Some(11))
val theType = getTypeTag(modifiedItem).tpe

If you print out theType above, it will be MyItem .

At this point if you try to use reflection to modify the field num , it's not going to work because MyItem has num as a method , not a field (as in MyItem.SubItem ):

val m = ru.runtimeMirror(modifiedItem.getClass.getClassLoader)
val numTermSymb = theType.decl(ru.TermName("num")).asTerm
val im = m.reflect(modifiedItem)
val numFieldMirror = im.reflectField(numTermSymb)  // not going to work
numFieldMirror.get
numFieldMirror.set(Some(999))  // my goal, if possible

Unfortunately, above will throw out scala.ScalaReflectionException: expected a field or an accessor method symbol, you provided method num .

Instead you should do the following:

val numTermSymb = theType.decl(ru.TermName("num")).asMethod
val im = m.reflect(modifiedItem)
val numFieldMirror = im.reflectMethod(numTermSymb)
numFieldMirror() // returns `Some(11)`

But my goal is to access the SubItem class that extends MyItem and modify its field . How can I get an instance of the type MyItem and modify the field in MyItem.SubItem that MyItem 's method num is accessing?

Replace

val theType = getTypeTag(modifiedItem).tpe

with

val theType = ru.typeOf[MyItem.SubItem]

if you know the name of class statically or with

val theType = m.staticClass("pckg.MyItem.SubItem").typeSignature

if you know the name of class dynamically.


Try

val className = modifiedItem.getClass.getName.replace('$', '.')
val theType = m.staticClass(className).typeSignature

Actually, m.staticClass(className).typeSignature is AnyRef with pckg.MyItem {...} ie parents/decls of SubItem

theType =:= ru.typeOf[MyItem.SubItem] // false

so, although numFieldMirror.get/set work, it's better to use toType instead of typeSignature

val className = modifiedItem.getClass.getName.replace('$', '.')
val theType = m.staticClass(className).toType

theType =:= ru.typeOf[MyItem.SubItem] // true

One more way is purely Scala-like

val instanceMirror = m.reflect(modifiedItem) 
val theType = instanceMirror.symbol.toType

theType =:= ru.typeOf[MyItem.SubItem] // true

It's even better because doesn't use error-prone and implementation-dependent operations on strings ( replace ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM