2009-09-24 4 views
44

In Scala, was ist die beste Möglichkeit, ein Objekt dynamisch zu instanziieren und eine Methode mit Reflektion aufzurufen?Scala: Wie kann ich ein Objekt dynamisch instanziieren und eine Methode mit Reflektion aufrufen?

Ich mag Scala-Äquivalent des folgenden Java-Code tun:

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

In dem obigen Code, sowohl die Klassennamen und der Name der Methode werden in dynamisch übergeben. Der obige Java-Mechanismus könnte wahrscheinlich für Foo und hello() verwendet werden, 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 die Scala-Methode erlaubt den Namen aller möglichen Symbole. Beide werden durch Namensmangel gelöst. Siehe Interop Between Java and Scala.

Ein weiteres Problem scheint die Übereinstimmung der Parameter durch Auflösung von Überlastungen und Autoboxing zu sein, beschrieben in Reflection from Scala - Heaven and Hell.

+2

Da die experimentelle Funktion in meiner Antwort nicht machen 2.8.0, wäre es besser, wenn eine andere Antwort als angenommen wurde markiert. –

+0

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

Antwort

57

Es ist ein einfacher Weg, Methode reflektiv aufzurufen, ohne zu fordern Java zurückgreifen Reflektionsmethoden: Verwenden Sie Structural Typing.

Einfach den Objektreferenz auf einen Strukturtyp umwandeln, der die erforderliche Methodensignatur hat, dann rufen Sie die Methode auf: keine Reflektion notwendig (scala macht natürlich 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" 
    } 
} 
+11

Strukturtyp wird mir nicht helfen, wenn ich den Methodennamen zur Kompilierzeit nicht kenne. –

+7

Die Verwendung eines Typenalias, um dem Strukturtyp einen Namen zu geben, verbessert oft die Lesbarkeit dieses Tricks. –

+1

Dies beantwortet nur einen Teil der Frage. Gibt es eine Scala zentrische Art der Durchführung von Method method = class.getMethod ("Hallo", null);? –

6

Die Instanciation Teil könnte die Manifest verwenden: siehe diese SO answer

experimental feature in Scala called manifests which are a way to get around a Java constraint regarding type erasure

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

With this version you still write

class Foo 
val t = new Test[Foo] 

However, if there's no no-arg constructor available you get a runtime exception instead of a static type error

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

die wahre Art sichere Lösung So würde eine Fabrik benutzen.


Hinweis: wie in this thread angegeben, Manifest hier zu bleiben, aber es ist jetzt „nur Gebrauch ist der Zugriff auf die Löschung des Typs, wie eine Klasseninstanz zu geben.“

The only thing manifests give you now is the erasure of the static type of a parameter at the call site (contrary to getClass which give you the erasure of the dynamic type).


Sie können dann eine Methode durch Reflexion erhalten:

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

und rufen Sie es

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 
12

Die Antworten von VonC und Walter Chang sind recht gut, so dass ich nur mit einer Scala 2.8 Experimentelle Funktion ergänzen. In der Tat werde ich nicht einmal versuchen, es anzuziehen, ich werde nur das Scaladoc kopieren.

object Invocation 
    extends AnyRef 

A more convenient syntax for reflective invocation. Example usage:

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

You can call it reflectively one of two ways:

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. 

If you call the oo method and do not give the type inferencer enough help, it will most likely infer Nothing, which will result in a ClassCastException.

Author Paul Phillips

+0

Dies ist eine Art von was ich aus dem verlinkten Blog-Artikel im Sinn hatte. Ein weiteres Puzzlestück ist der Name Mangling Service. –

+3

Leider hat Invocation es nicht geschafft: https://lampsvn.epfl.ch/trac/scala/changeset/19695 –

3

Im Fall, dass Sie ein Verfahren zur Herstellung einer Scala 2.10 Objekt aufzurufen (nicht-Klasse), und Sie haben die Namen der Methode und Objekt als String s, können Sie es so tun:

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 Standardausgabe. Weitere Details in Scala Documentation.

+1

Was ist mit einer Klasse, kein Objekt? – matanster

4

Aufarbeitung von @ nedim Antwort, hier ist eine Basis für eine vollständige Antwort, Hauptunterschied unterhalb wir naiv Klassen instanziiert. Dieser Code behandelt nicht den Fall mehrerer 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

Verwandte Folge: http://stackoverflow.com/questions/34227984/perfectly-handling-scala-constructors-in-dynamic-instantiation – matanster

Verwandte Themen