调用静态方法只能访问包含对象的类型

Invoke static method with access only to the containing object's type

我有几个对象之一的 TypeTag,我知道它们扩展了一个具有已知方法签名的基本特征。如果需要,我还可以访问该方法的 MethodSymbol。我想做的是:

def invokeMyMethod[T: TypeTag](myMethodSymbol: MethodSymbol): String = {
  // I know T has structural type { def myMethod: String }
  // so I want the result of calling T.myMethod but I don't have
  // access to the actual object T, only its type.
}

因为我知道该类型代表一个对象,所以我知道该方法是静态的,所以我只需要访问单例实例就可以调用该方法。不幸的是,我找不到从类型到实例的方法。

我知道我可以从 runtimeMirror 获得一个 RuntimeClass 实例,但我不确定在获得它后如何处理 class。它似乎本质上具有 AnyRef 类型,所以我尝试投射它但没有成功:

def invokeMyMethod[T: TypeTag]: Any = {
  val runtimeT = runtimeMirror(getClass.getClassLoader).runtimeClass(T)
  runtimeT.asInstanceOf[{ def myMethod: String }].myMethod
  // Error invoking method 'myMethod'
}

我也知道我可以从我的 ClassSymbol 中得到一个 ClassMirror 但这似乎只能访问构造函数 MethodMirror 如果我的项目是一个对象则这无济于事而不是 class:

def invokeMyMethod[T: TypeTag](myMethodSymbol: MethodSymbol): Any = {
  val mirror = runtimeMirror(getClass.getClassLoader)
  val runtimeT = mirror.runtimeClass(T)
  val mirrorT = mirror.reflect(runtimeT)
  mirrorT.reflectMethod(myMethodSymbol)()
  // Expected a member of class Class, you provided value T.myMethod
}

而且我知道如果我有 T 的实际运行时实例,使用 InstanceMirror 会很容易,但我不知道如何获得我的 InstanceMirror对象类型。

尝试

import scala.reflect.runtime.universe._
import scala.reflect.runtime

trait BaseTrait {
  def myMethod: String
}

object MyObject extends BaseTrait {
  override def myMethod: String = "MyObject.myMethod"
}

def invokeMyMethod[T: TypeTag]: String = {
  val typ = typeOf[T]
  val moduleSymbol   = typ.termSymbol.asModule
  val methodSymbol   = typ.decl(TermName("myMethod")).asMethod
  val runtimeMirror  = runtime.currentMirror
  val moduleMirror   = runtimeMirror.reflectModule(moduleSymbol)
  val instance       = moduleMirror.instance
  val instanceMirror = runtimeMirror.reflect(instance)
  val methodMirror   = instanceMirror.reflectMethod(methodSymbol)
  methodMirror().asInstanceOf[String]
}

invokeMyMethod[MyObject.type] // MyObject.myMethod

如果对象嵌套成class试试

class Outer {
  object `_` extends BaseTrait {
    override def myMethod: String = "_.myMethod"
  }
}

def invokeMyMethod[T: TypeTag]: String = {
  val typ = typeOf[T]
  val runtimeMirror          = runtime.currentMirror
  val moduleSymbol           = typ.termSymbol.asModule
  val outerClassSymbol       = moduleSymbol.owner.asClass
  val outerClassType         = outerClassSymbol.typeSignature 
  val outerConstructorSymbol = outerClassType.decl(termNames.CONSTRUCTOR).asMethod
  val outerClassMirror       = runtimeMirror.reflectClass(outerClassSymbol)
  val outerConstructorMirror = outerClassMirror.reflectConstructor(outerConstructorSymbol)
  val outerInstance          = outerConstructorMirror() // if class Outer has no-arg constructor
  val outerInstanceMirror    = runtimeMirror.reflect(outerInstance)
  val moduleMirror           = outerInstanceMirror.reflectModule(moduleSymbol)
  val methodSymbol           = typ.decl(TermName("myMethod")).asMethod
  val instance               = moduleMirror.instance
  val instanceMirror         = runtimeMirror.reflect(instance)
  val methodMirror           = instanceMirror.reflectMethod(methodSymbol)
  methodMirror().asInstanceOf[String]
}

val outer = new Outer
invokeMyMethod[outer.`_`.type] // _.myMethod

如果 Outer 是特征(抽象 class)而不是 class,您可以使用工具箱

trait Outer {
  object `_` extends BaseTrait {
    override def myMethod: String = "_.myMethod"
  }
}

def invokeMyMethod[T: TypeTag]: String = {
  val typ = typeOf[T]
  val runtimeMirror  = runtime.currentMirror
  val toolbox = runtimeMirror.mkToolBox()
  val outerClassSymbol = toolbox.define(
    q"class OuterImpl extends com.example.Outer".asInstanceOf[ClassDef]
  ).asClass
  toolbox.eval(q"(new $outerClassSymbol).`_`.myMethod").asInstanceOf[String]
}

val outer = new Outer {}
invokeMyMethod[outer.`_`.type] // _.myMethod

def invokeMyMethod[T: TypeTag]: String = {
  val typ = typeOf[T]
  val runtimeMirror  = runtime.currentMirror
  val toolbox = runtimeMirror.mkToolBox()
  val toolboxMirror  = toolbox.mirror
  val moduleSymbol   = typ.termSymbol.asModule
  val outerClassSymbol = toolbox.define(
    q"class OuterImpl extends com.example.Outer".asInstanceOf[ClassDef]
  ).asClass
  val outerClassType = outerClassSymbol.typeSignature
  val outerConstructorSymbol = outerClassType.decl(termNames.CONSTRUCTOR).asMethod
  val outerClassMirror = toolboxMirror.reflectClass(outerClassSymbol)
  val outerConstructorMirror = outerClassMirror.reflectConstructor(outerConstructorSymbol)
  val outerInstance = outerConstructorMirror()
  val outerInstanceMirror = runtimeMirror.reflect(outerInstance)
  val moduleMirror = outerInstanceMirror.reflectModule(moduleSymbol)
  val methodSymbol   = typ.decl(TermName("myMethod")).asMethod
  val instance       = moduleMirror.instance
  val instanceMirror = toolboxMirror.reflect(instance)
  val methodMirror   = instanceMirror.reflectMethod(methodSymbol)
  methodMirror().asInstanceOf[String]
}

val outer = new Outer {}
invokeMyMethod[outer.`_`.type] // _.myMethod

或者如果你可以使用现有的外部实例class/trait试试

val outer = new Outer

def invokeMyMethod[T: TypeTag]: String = {
  val typ = typeOf[T]
  val runtimeMirror       = runtime.currentMirror
  val moduleSymbol        = typ.termSymbol.asModule
  val outerInstanceMirror = runtimeMirror.reflect(outer)
  val moduleMirror        = outerInstanceMirror.reflectModule(moduleSymbol)
  val methodSymbol        = typ.decl(TermName("myMethod")).asMethod
  val instance            = moduleMirror.instance
  val instanceMirror      = runtimeMirror.reflect(instance)
  val methodMirror        = instanceMirror.reflectMethod(methodSymbol)
  methodMirror().asInstanceOf[String]
}

invokeMyMethod[outer.`_`.type] // _.myMethod

其实可以用outer解构输入类型

def invokeMyMethod[T: TypeTag]: String = {
  val typ = typeOf[T]
  val outerSymbol = typ match {
    case SingleType(pre, _) => pre.termSymbol
  }
  val runtimeMirror  = runtime.currentMirror
  val toolbox = runtimeMirror.mkToolBox()
  toolbox.eval(q"$outerSymbol.`_`.myMethod").asInstanceOf[String]
}

val outer = new Outer
invokeMyMethod[outer.`_`.type] // _.myMethod