2009-09-24 10 views
44

W Scala, jaki jest najlepszy sposób dynamicznego tworzenia obiektu i wywoływania metody za pomocą odbicia?Scala: Jak dynamicznie utworzyć instancję obiektu i wywołać metodę za pomocą odbicia?

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

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 i nazwa metody są przekazywane w dynamicznie. Powyższy mechanizm Java może być prawdopodobnie użyty dla Foo i hello(), ale typy Scali nie pasują jeden do jednego z Java. Na przykład klasa może być zadeklarowana niejawnie dla obiektu singleton. Również metoda Scala umożliwia nazwanie wszystkich rodzajów symboli. Oba są rozwiązywane przez mangling nazw. Zobacz Interop Between Java and Scala.

Inną kwestią wydaje się być dopasowanie parametrów przez rozwiązywanie przeciążeń i autoboxing, opisane w Reflection from Scala - Heaven and Hell.

+2

Biorąc pod uwagę, że funkcja eksperymentalna w mojej odpowiedzi nie sprawiają 2.8.0, byłoby lepiej, gdyby inna odpowiedź została oznaczona jako zaakceptowana. –

+0

jeśli mam klasę z parametrami dla klasy takiej jak klasa MailServerice (emailIds: string) czy możliwe jest wywoływanie dynamiczne w czasie wykonywania? – ashK

Odpowiedz

57

Jest łatwiejszy sposób, aby wywołać metodę refleksyjnie, bez uciekania się do wywołującego Java metody refleksyjne: użyj typowania strukturalnego.

Wystarczy rzucić referencję obiektu do typu strukturalnego, który ma niezbędną sygnaturę metody, a następnie wywołać metodę: nie jest wymagane odbicie (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" 
    } 
} 
+11

Typ strukturalny nie pomoże mi, jeśli nie znam nazwy metody w czasie kompilacji. –

+7

użycie aliasu typu, aby nadać typowi strukturalnemu nazwę, często poprawia czytelność tej sztuczki. –

+1

To odpowiada tylko na część pytania. Czy istnieje Scala centric sposób wykonywania Method method = class.getMethod ("hello", null) ;? –

6

instanciation część mógłby użyć Manifest: patrz ten 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) 

tak więc bezpieczne rozwiązanie True Type używałaby Fabryki.


Uwaga: jak podano w this thread, Oczywisty jest tu zatrzymać, ale to na razie „tylko stosowanie jest zapewnienie dostępu do wymazania typem instancji klasy.”

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


Można wówczas uzyskać metody poprzez odbicie:

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

i powołać go

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

Odpowiedzi autorstwa VonC i Walter Chang są dość dobre, więc ja po prostu uzupełniać jednym Scala 2,8 Experimental funkcji. Właściwie nawet nie będę się starał ubierać, po prostu skopiuję skaladoc.

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

To jest coś, co miałem na myśli z połączonego artykułu na blogu. Kolejnym elementem układanki jest usługa mordowania nazwy. –

+3

Niestety, wywołanie nie zakończyło się: https://lampsvn.epfl.ch/trac/scala/changeset/19695 –

3

W przypadku trzeba wywołać metodę o Scala 2.10 obiektu (nie klasy) i masz nazwy metody i przedmiotu jako String s, możesz to zrobić tak:

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

Powoduje wydrukowanie 5 na standardowe wyjście. Dalsze szczegóły w Scala Documentation.

+1

Co z klasą, a nie obiektem? – matanster

4

Opracowanie odpowiedzi @ nedim, tutaj jest podstawa dla pełnej odpowiedzi, główna różnica jest tutaj poniżej tworzymy instynkty naiwne. Ten kod nie obsługuje przypadku wielu konstruktorów i nie jest w żaden sposób 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 że go 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" 
    ) 
) 
+1

powiązane śledzenie: http://stackoverflow.com/questions/34227984/perfectly-handling-scala-constructors-in-dynamic-instantiation – matanster