Вызов метода через отражение в Scala - PullRequest
0 голосов
/ 22 ноября 2018

Я хочу вызвать произвольный публичный метод произвольного материала с помощью рефлексии.То есть, скажем, я хочу написать метод extractMethod, который будет использоваться как:

class User { def setAvatar(avatar: Avatar): Unit = …; … }

val m = extractMethod(someUser, "setAvatar")
m(someAvatar)

Из Отражения.Обзор документа из Scala docs, я вижу следующий прямой способ сделать это:

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

def extractMethod[Stuff: ClassTag: TypeTag](
  stuff:      Stuff,
  methodName: String): MethodMirror =
  {
    val stuffTypeTag = typeTag[Stuff]
    val mirror = stuffTypeTag.mirror
    val stuffType = stuffTypeTag.tpe
    val methodSymbol = stuffType
      .member(TermName(methodName)).asMethod
    mirror.reflect(stuff)
      .reflectMethod(methodSymbol)
  }

Однако, что меня беспокоит это решение, так это то, что мне нужно передать неявные ClassTag[Stuff] и TypeTag[Stuff] параметры (первый нужен для вызова reflect, второй - для получения stuffType).Что может быть довольно громоздким, особенно если extractMethod вызывается из обобщенных элементов, которые вызываются из обобщенных элементов и так далее.Я бы принял это как необходимость для некоторых языков, в которых сильно отсутствует информация о типах среды выполнения, но Scala основана на JRE, что позволяет делать следующее:

def extractMethod[Stuff](
  stuff:          Stuff,
  methodName:     String,
  parameterTypes: Array[Class[_]]): (Object*) => Object =
    {
      val unboundMethod = stuff.getClass()
        .getMethod(methodName, parameterTypes: _*)
      arguments => unboundMethod(stuff, arguments: _*)
    }

Я понимаю, что отражение в Scala позволяет получить больше информацииэто основное отражение Java.Тем не менее, здесь мне просто нужно вызвать метод.Есть ли способ как-то уменьшить требования (например, ClassTag, TypeTag) версии extractMethod на основе отражения Scala (без возврата к отражению на чистой Java), предполагая, что производительность для меня не имеет значения

1 Ответ

0 голосов
/ 22 ноября 2018

Да, есть.

  1. Во-первых, согласно этот ответ , TypeTag[Stuff] является строго более строгим требованием, чем ClassTag[Stuff].Хотя мы не получаем автоматически неявное ClassTag[Stuff] из неявного TypeTag[Stuff], мы можем оценить его вручную как ClassTag[Stuff](stuffTypeTag.mirror.runtimeClass(stuffTypeTag.tpe)), а затем неявно или явно передать его в reflect, который в этом нуждается:

    import scala.reflect.ClassTag
    import scala.reflect.runtime.universe._
    
    def extractMethod[Stuff: TypeTag](
      stuff:      Stuff,
      methodName: String): MethodMirror =
      {
        val stuffTypeTag = typeTag[Stuff]
        val mirror = stuffTypeTag.mirror
        val stuffType = stuffTypeTag.tpe
        val stuffClassTag = ClassTag[Stuff](mirror.runtimeClass(stuffType))
        val methodSymbol = stuffType
          .member(TermName(methodName)).asMethod
        mirror.reflect(stuff)(stuffClassTag)
          .reflectMethod(methodSymbol)
      }
    
  2. Во-вторых, mirror и stuffType можно получить из stuff.getClass():

    import scala.reflect.ClassTag
    import scala.reflect.runtime.universe._
    
    def extractMethod(stuff: Stuff, methodName: String): MethodMirror = {
      val stuffClass = stuff.getClass()
      val mirror = runtimeMirror(stuffClass.getClassLoader)
      val stuffType = mirror.classSymbol(stuffClass).toType
      val stuffClassTag = ClassTag[Stuff](mirror.runtimeClass(stuffType))
      val methodSymbol = stuffType
        .member(TermName(methodName)).asMethod
      mirror.reflect(stuff)(stuffClassTag)
        .reflectMethod(methodSymbol)
    }
    

Поэтому мы получили отражающие объекты в стиле Scala (то есть, наконец, MethodMirror), не требуя явной или неявной передачи от вызывающей стороны ClassTag и / или TypeTag.Однако не уверен, как он соотносится со способами, описанными в этом вопросе (т.е. передачей тегов извне и чистой Java) с точки зрения производительности.

...