【问题标题】:Scala accessing members of runtime types using reflection of a class that extends a traitScala使用扩展特征的类的反射访问运行时类型的成员
【发布时间】:2021-01-03 20:56:48
【问题描述】:

假设我有一个MyItem trait,它的伴生对象有一个apply() 函数,该函数创建一个名为SubItem 的类实例,该实例从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 类并修改其字段。如何获取MyItem 类型的实例并修改MyItem 的方法num 正在访问的MyItem.SubItem 中的字段?

【问题讨论】:

  • 为什么需要这样做? runtime reflection 不鼓励使用,原因有很多。您是否完全确定您的元问题只能使用 reflection 来解决。
  • @LuisMiguelMejíaSuárez 我个人会尽可能避免使用反射。但是这种方法是我团队中的高级工程师目前正在推动的
  • 听到这个消息很难过,我很确定有人会来为这个问题提供一个很好的答案,但我仍然鼓励你打开另一个描述元问题的问题,它可能会对你有所帮助说服那个“高级”使用不同的方法,或者至少它会帮助你在未来成为一个更好的开发者。
  • 谢谢,我会进一步探索并告诉他我对这件事的看法

标签: scala reflection scala-reflect


【解决方案1】:

替换

val theType = getTypeTag(modifiedItem).tpe

val theType = ru.typeOf[MyItem.SubItem]

如果你知道类的名字是静态的还是用的

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

如果你动态知道类的名称。


试试

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

实际上,m.staticClass(className).typeSignatureAnyRef with pckg.MyItem {...}SubItem 的父母/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

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

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

它甚至更好,因为它不对字符串使用容易出错和依赖于实现的操作 (replace)。

【讨论】:

  • 除非我不能那样做。 :( 我在那里简化了代码,但是采用modifiedItem 的实际函数是泛型类型T,我们需要在运行时使用反射找出类型,例如def myFunc[T:ClassTag](modifiedItem: T)(implicit tag:ru.TypeTag[T]): T = { val theType = getTypeTag(modifiedItem).tpe // blah blah do the rest here }
  • @Hush 查看更新staticClass。如果您仍然不能这样做,那么您应该编辑您的问题,提供与您的实际用例相对应的最少代码。
  • 这就是我想要的。谢谢!
  • @Hush 实际上,最好使用toType 而不是typeSignature。查看更新。
  • @Hush 还有一种方法是val instanceMirror = m.reflect(modifiedItem) val theType = instanceMirror.symbol.toType。更好,因为不使用字符串操作 (replace)。
猜你喜欢
  • 2020-04-30
  • 2016-11-28
  • 2013-04-23
  • 1970-01-01
  • 1970-01-01
  • 2011-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多