Scala: jak dynamicznie utworzyć instancję obiektu i wywołać metodę za pomocą reflection?

W Scali, jaki jest najlepszy sposób dynamicznego tworzenia instancji obiektu i wywoływania metody za pomocą odbicia?

Chciałbym zrobić Scala-odpowiednik następującego kodu Javy:

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

W powyższym kodzie zarówno nazwa klasy, jak i nazwa metody są przekazywane dynamicznie. Powyższy Mechanizm Java może być prawdopodobnie użyty dla Foo i hello(), ale typy Scali nie pasują jeden do jednego z tym Java. Na przykład, klasa może być zadeklarowana domyślnie dla obiektu singleton. Również metoda Scala pozwala na nazwanie WSZYSTKICH rodzajów symboli. Oba są rozwiązywane przez namaglowanie nazwy. Zobacz interakcję pomiędzy Javą a scalą .

[[3]}Kolejną kwestią wydaje się dopasowanie parametrów poprzez rozwiązywanie przeciążeń i autoboxing, opisane w Reflection from Scala - Heaven and Hell.
Author: Eugene Yokota, 2009-09-24

5 answers

Istnieje łatwiejszy sposób wywoływania metody refleksyjnej bez uciekania się do wywoływania metod refleksyjnych Javy: użyj typowania strukturalnego.

Wystarczy rzucić odniesienie do obiektu do typu strukturalnego, który ma wymaganą sygnaturę metody, a następnie wywołać metodę: no reflection necessary (oczywiście Scala robi odbicie pod spodem, ale nie musimy tego robić).

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"
  }
}
 61
Author: Walter Chang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-24 07:15:07

Odpowiedzi VonC i Walter Chang są całkiem dobre, więc uzupełnię je o jedną eksperymentalną funkcję Scali 2.8. W zasadzie, nawet nie będę się starał go ubrać, tylko skopiuję scaladoc.

object Invocation
  extends AnyRef

Wygodniejsza składnia refleksu inwokacja. Przykład użycia:

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

Można to nazwać refleksyjnie jednym z dwa sposoby:

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.
Jeśli zadzwonisz do oo sposób i nie podawaj typu inferencer wystarczy pomocy, to będzie najbardziej prawdopodobnie nic nie wnioskować, co będzie wynik w ClassCastException.

Autor Paul Phillips

 12
Author: Daniel C. Sobral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:13

Część instancjowa może użyć Manifest: Zobacz to więc odpowiedz

W Scali istnieją dwa rodzaje manifestów, które są sposobem na obejście ograniczeń Javy dotyczących usuwania typów.]}
 class Test[T](implicit m : Manifest[T]) {
   val testVal = m.erasure.newInstance().asInstanceOf[T]
 }

Z tą wersją nadal piszesz

class Foo
val t = new Test[Foo]

Jeśli jednak nie jest dostępny konstruktor no-arg, otrzymasz wyjątek runtime zamiast typu statycznego błąd

scala> new Test[Set[String]] 
java.lang.InstantiationException: scala.collection.immutable.Set
at java.lang.Class.newInstance0(Class.java:340)
Więc prawdziwym bezpiecznym rozwiązaniem byłoby użycie fabryki.

Uwaga: jak stwierdzono w ten wątek, Manifest jest tutaj, aby pozostać, ale jest na razie " jedynym użyciem jest dać dostęp do kasowania typu jako instancji klasy."

Jedyną rzeczą, która daje Ci teraz, jest usunięcie typu statycznego parametru w miejscu wywołania (w przeciwieństwie do getClass, które dają usunięcietypu dynamicznego ).


Można wtedy uzyskać metodę poprzez odbicie:

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

I wywołaj go

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

scala>val a = new A
a: A = A@1f854bd

scala>a.getClass.getMethod(decode("foo_="),
classOf[Boolean]).invoke(a, java.lang.Boolean.TRUE)
res15: java.lang.Object = bar 
 6
Author: VonC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:34

Jeśli chcesz wywołać metodę obiektu Scala 2.10 (Nie klasy) i masz nazwy metody i obiektu jako String s, możesz to zrobić w następujący sposób:

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)
  }
}

Wyświetla 5 na standardowe wyjście. Więcej szczegółów w dokumentacji Scali .

 5
Author: nedim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-21 16:02:46

Praca z odpowiedzi @nedim, oto podstawa dla pełnej odpowiedzi, główną różnicą jest to, że poniżej tworzymy naiwne klasy. Ten kod nie obsługuje przypadku wielu konstruktorów i w żadnym wypadku nie jest pełną odpowiedzią.

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", "")
}

Oto build.sbt, który by ją skompilował:

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"
    )
  )
 4
Author: matanster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-11 16:09:54