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
,它不会起作用,因为 MyItem
有 num
作为 方法,不是字段(如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.SubItem
中 MyItem
的方法 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).typeSignature
是 AnyRef 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 操作。
假设我有一个 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
,它不会起作用,因为 MyItem
有 num
作为 方法,不是字段(如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.SubItem
中 MyItem
的方法 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).typeSignature
是 AnyRef with pckg.MyItem {...}
即 SubItem
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 操作。