Scala 使用扩展特征的 class 的反射访问运行时类型的成员

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

假设我有一个 MyItem 特征,它的伴生对象有一个 apply() 函数,它创建一个名为 SubItem 的 class 实例,它扩展自 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

如果打印出上面的theType,就是MyItem.

此时如果您尝试使用 reflection 修改字段 num,它不会起作用,因为 MyItemnum 作为 方法,不是字段(如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

不幸的是,上面会抛出scala.ScalaReflectionException: expected a field or an accessor method symbol, you provided method num.

相反,您应该执行以下操作:

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

但我的目标是访问扩展 MyItem 的 SubItem class 并修改其字段。如何获取类型 MyItem 的实例并修改 MyItem.SubItemMyItem 的方法 num 正在访问的字段?

替换

val theType = getTypeTag(modifiedItem).tpe

val theType = ru.typeOf[MyItem.SubItem]

如果您知道 class 的静态名称或

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

如果您动态知道 class 的名字。


尝试

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

实际上,m.staticClass(className).typeSignatureAnyRef with pckg.MyItem {...}SubItem

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

所以,虽然 numFieldMirror.get/set 有效,但最好使用 toType 而不是 typeSignature

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

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

还有一种方式就是纯Scala-like

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

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

更好,因为不对字符串 (replace) 使用 error-prone 和 implementation-dependent 操作。