Подтвердить что ты не робот

Scala: Как динамически создать экземпляр объекта и вызвать метод с использованием отражения?

В Scala, какой лучший способ динамически создать экземпляр объекта и вызвать метод с использованием отражения?

Я хотел бы сделать Scala -эквивалент следующего кода Java:

Class class = Class.forName("Foo");
Object foo = class.newInstance();
Method method = class.getMethod("hello", null);
method.invoke(foo, null);

В приведенном выше коде имя класса и имя метода передаются динамически. Вышеупомянутый механизм Java, вероятно, можно использовать для Foo и hello(), но типы Scala не совпадают друг с другом с Java. Например, класс может быть объявлен неявно для одноэлементного объекта. Кроме того, метод Scala позволяет использовать все виды символов. Оба разрешаются путем изменения имени. См. Взаимодействие между Java и Scala.

Другой проблемой является сопоставление параметров путем разрешения перегрузок и автобоксинга, описанных в Reflection from Scala - Heaven and Hell.

4b9b3361

Ответ 1

Существует более простой способ обратного вызова метода, не прибегая к вызовам методов отражения Java: используйте Structural Typing.

Просто присвойте ссылку на объект Структурному типу, который имеет необходимую сигнатуру метода, затем вызовите метод: не требуется никакого отражения (конечно, Scala делает отражение внизу, но нам не нужно это делать).

class Foo {
  def hello(name: String): String = "Hello there, %s".format(name)
}

object FooMain {

  def main(args: Array[String]) {
    val foo  = Class.forName("Foo").newInstance.asInstanceOf[{ def hello(name: String): String }]
    println(foo.hello("Walter")) // prints "Hello there, Walter"
  }
}

Ответ 2

Ответы VonC и Вальтера Чанга неплохие, поэтому я просто добавлю с одним Scala 2.8. Экспериментальная особенность. На самом деле, я даже не буду одеваться, я просто скопирую scaladoc.

object Invocation
  extends AnyRef

Более удобный синтаксис для рефлексивного призывание. Пример использования:

class Obj { private def foo(x: Int, y: String): Long = x + y.length }

Вы можете называть это рефлексивно одним из двумя способами:

import scala.reflect.Invocation._
(new Obj) o 'foo(5, "abc")                 // the 'o' method returns Any
val x: Long = (new Obj) oo 'foo(5, "abc")  // the 'oo' method casts to expected type.

Если вы называете оо метод и не дают тип inferencer достаточно поможет, он будет Вероятно, вывести ничего, что приведет к исключению ClassCastException.

Автор Пол Филлипс

Ответ 3

Часть instanciation может использовать Манифест: см. этот Ответ SO >

экспериментальная особенность в Scala называется манифестами, которые являются способом обойти ограничение Java относительно стирания типа

 class Test[T](implicit m : Manifest[T]) {
   val testVal = m.erasure.newInstance().asInstanceOf[T]
 }

В этой версии вы все еще пишете

class Foo
val t = new Test[Foo]

Однако, если нет конструктора no-arg, вы получаете исключение во время выполнения вместо ошибки статического типа

scala> new Test[Set[String]] 
java.lang.InstantiationException: scala.collection.immutable.Set
at java.lang.Class.newInstance0(Class.java:340)

Таким образом, истинное безопасное решение будет использовать Factory.


Примечание: как указано в этот поток, Manifest здесь, чтобы остаться, но на данный момент "использовать только для доступа к стирание типа как экземпляр класса."

Единственное, что проявляется теперь, - это стирание статического типа параметра на сайте вызова (в отличие от getClass, который дает вам стирание динамического типа).


Затем вы можете получить метод через отражение:

classOf[ClassName].getMethod("main", classOf[Array[String]]) 

и вызывать его

scala> class A {
     | def foo_=(foo: Boolean) = "bar"
     | }
defined class A

scala>val a = new A
a: A = [email protected]

scala>a.getClass.getMethod(decode("foo_="),
classOf[Boolean]).invoke(a, java.lang.Boolean.TRUE)
res15: java.lang.Object = bar 

Ответ 4

Исходя из ответа @nedim, вот основа для полного ответа, Основное различие здесь ниже, мы создаем наивные классы. Этот код не обрабатывает случай с несколькими конструкторами и отнюдь не является полным ответом.

import scala.reflect.runtime.universe

case class Case(foo: Int) {
  println("Case Case Instantiated")
}

class Class {
  println("Class Instantiated")
}

object Inst {

  def apply(className: String, arg: Any) = {
    val runtimeMirror: universe.Mirror = universe.runtimeMirror(getClass.getClassLoader)

    val classSymbol: universe.ClassSymbol = runtimeMirror.classSymbol(Class.forName(className))

    val classMirror: universe.ClassMirror = runtimeMirror.reflectClass(classSymbol)

    if (classSymbol.companion.toString() == "<none>") // TODO: use nicer method "hiding" in the api?
    {
      println(s"Info: $className has no companion object")
      val constructors = classSymbol.typeSignature.members.filter(_.isConstructor).toList
      if (constructors.length > 1) { 
        println(s"Info: $className has several constructors")
      } 
      else {
        val constructorMirror = classMirror.reflectConstructor(constructors.head.asMethod) // we can reuse it
        constructorMirror()
      }

    }
    else
    {
      val companionSymbol = classSymbol.companion
      println(s"Info: $className has companion object $companionSymbol")
      // TBD
    }

  }
}

object app extends App {
  val c = Inst("Class", "")
  val cc = Inst("Case", "")
}

Вот build.sbt, который бы скомпилировал его:

lazy val reflection = (project in file("."))
  .settings(
    scalaVersion := "2.11.7",
    libraryDependencies ++= Seq(
      "org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided",
      "org.scala-lang" % "scala-library" % scalaVersion.value % "provided"
    )
  )

Ответ 5

Если вам нужно вызвать метод объекта Scala 2.10 (а не класс), и у вас есть имена метода и объекта как String s, вы можете сделать это следующим образом:

package com.example.mytest

import scala.reflect.runtime.universe

class MyTest

object MyTest {

  def target(i: Int) = println(i)

  def invoker(objectName: String, methodName: String, arg: Any) = {
    val runtimeMirror = universe.runtimeMirror(getClass.getClassLoader)
    val moduleSymbol = runtimeMirror.moduleSymbol(
      Class.forName(objectName))

    val targetMethod = moduleSymbol.typeSignature
      .members
      .filter(x => x.isMethod && x.name.toString == methodName)
      .head
      .asMethod

    runtimeMirror.reflect(runtimeMirror.reflectModule(moduleSymbol).instance)
      .reflectMethod(targetMethod)(arg)
  }

  def main(args: Array[String]): Unit = {
    invoker("com.example.mytest.MyTest$", "target", 5)
  }
}

Отпечатает 5 до стандартного вывода. Дальнейшие подробности в Scala Документация.