55 Stimmen

Scala: Wie kann ich ein Objekt dynamisch instanziieren und eine Methode mit Reflection aufrufen?

Wie kann man in Scala am besten ein Objekt dynamisch instanziieren und eine Methode mit Reflection aufrufen?

Ich würde gerne das Scala-Äquivalent des folgenden Java-Codes erstellen:

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

In dem obigen Code werden sowohl der Klassenname als auch der Methodenname dynamisch übergeben. Der obige Java-Mechanismus könnte wahrscheinlich auch für Foo y hello() , aber die Scala-Typen stimmen nicht eins zu eins mit denen von Java überein. Zum Beispiel kann eine Klasse implizit für ein Singleton-Objekt deklariert werden. Auch Scala-Methoden können alle Arten von Symbolen als Namen haben. Beides wird durch Name Mangling gelöst. Siehe Interop zwischen Java und Scala .

Ein weiteres Problem scheint der Abgleich von Parametern durch Auflösen von Überladungen und Autoboxing zu sein, beschrieben in Überlegungen aus Scala - Himmel und Hölle .

2 Stimmen

Da die experimentelle Funktion in meiner Antwort nicht in 2.8.0 aufgenommen wurde, wäre es besser, wenn eine andere Antwort als akzeptiert markiert würde.

0 Stimmen

Wenn ich eine Klasse mit Parametern für eine Klasse wie class MailServerice(emailIds : string) habe, ist es möglich, dynamisch zur Laufzeit aufzurufen?

73voto

Walter Chang Punkte 11399

Es gibt einen einfacheren Weg, Methoden reflektiv aufzurufen, ohne auf den Aufruf von Java-Reflection-Methoden zurückzugreifen: Verwenden Sie Structural Typing.

Man castet einfach die Objektreferenz in einen Strukturtyp, der die notwendige Methodensignatur hat, und ruft dann die Methode auf: keine Reflektion notwendig (natürlich macht Scala die Reflektion darunter, aber wir müssen es nicht tun).

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

15 Stimmen

Structural Type hilft mir nicht, wenn ich den Methodennamen zur Kompilierzeit nicht kenne.

7 Stimmen

Die Verwendung eines Typ-Alias, um dem Strukturtyp einen Namen zu geben, verbessert oft die Lesbarkeit dieses Tricks.

2 Stimmen

Dies beantwortet nur einen Teil der Frage. Gibt es einen Scala-zentrierten Weg zur Ausführung von Methode method = class.getMethod("hello", null);?

12voto

Daniel C. Sobral Punkte 290004

Die Antworten von VonC y Walter Chang sind ziemlich gut, also werde ich sie nur mit einer experimentellen Funktion von Scala 2.8 ergänzen. Tatsächlich werde ich mir nicht einmal die Mühe machen, es aufzuhübschen, ich werde einfach das Scaladoc kopieren.

object Invocation
  extends AnyRef

Eine bequemere Syntax für reflektierende Aufrufen. Beispiel für die Verwendung:

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

Man kann sie reflektierend als eine der zwei Möglichkeiten:

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.

Wenn Sie das oo Methode auf und geben dem Typ nicht genügend Hilfe geben, wird er höchstwahrscheinlich wahrscheinlich Nothing ableiten, was zu was zu einer ClassCastException führt.

Autor Paul Phillips

0 Stimmen

Das ist in etwa das, was ich mir bei dem verlinkten Blogartikel gedacht habe. Ein weiteres Teil des Puzzles ist der Namensveränderungsdienst.

5 Stimmen

Leider hat es Invocation nicht in die Endauswahl geschafft: lampsvn.epfl.ch/trac/scala/changeset/19695

7voto

VonC Punkte 1117238

Der Einleitungsteil pourrait verwenden Sie die Manifest : siehe dies SO antworten

eine experimentelle Funktion in Scala, Manifeste genannt, mit der eine Java-Beschränkung in Bezug auf das Löschen von Typen umgangen werden kann

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

Bei dieser Version schreiben Sie noch

class Foo
val t = new Test[Foo]

Wenn jedoch kein no-arg-Konstruktor verfügbar ist, erhalten Sie eine Laufzeitausnahme anstelle eines statischen Typfehlers

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

Die wirklich typsichere Lösung wäre also die Verwendung einer Factory.


Anmerkung: wie in dieses Thema Manifest ist hier zu bleiben, aber ist für jetzt "nur verwenden, um den Zugang zu der Löschung des Typs als Klasse Instanz zu geben".

Das Einzige, was die Manifeste Ihnen jetzt geben, ist die Auslöschung der statisch Typ eines Parameters an der Aufrufstelle (im Gegensatz zu getClass die Ihnen die Löschung der dynamisch Typ).


Sie können dann eine Methode durch Reflexion erhalten:

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

und rufen sie auf

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

7voto

nedim Punkte 1637

Für den Fall, dass Sie eine Methode eines Scala 2.10-Objekts (nicht einer Klasse) aufrufen müssen und Sie die Namen der Methode und des Objekts als String s, können Sie so vorgehen:

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

Dies druckt 5 auf die Standardausgabe. Weitere Einzelheiten unter Scala-Dokumentation .

2 Stimmen

Wie wäre es mit einer Klasse, nicht mit einem Objekt?

5voto

matanster Punkte 14503

Ausgehend von der Antwort von @nedim, hier eine Basis für eine vollständige Antwort, Der Hauptunterschied besteht darin, dass wir hier unten naive Klassen instanziieren. Dieser Code behandelt nicht den Fall von mehreren Konstruktoren und ist keineswegs eine vollständige Antwort.

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

Hier ist ein build.sbt die es kompilieren würde:

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

1 Stimmen

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X