2009-09-24 27 views
44

En Scala, ¿cuál es la mejor manera de crear instancias dinámicas de un objeto e invocar un método mediante la reflexión?Scala: ¿Cómo puedo instanciar dinámicamente un objeto e invocar un método usando reflection?

me gustaría hacer Scala equivalente del siguiente código de Java:

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

En el código anterior, tanto el nombre de la clase y el nombre del método se pasan en forma dinámica. El mecanismo de Java anterior probablemente podría usarse para Foo y hello(), pero los tipos de Scala no coinciden uno a uno con los de Java. Por ejemplo, una clase puede declararse implícitamente para un objeto singleton. También el método de Scala permite que todo tipo de símbolos sea su nombre. Ambos se resuelven por cambio de nombre. Ver Interop Between Java and Scala.

Otro problema parece ser la coincidencia de parámetros mediante la resolución de sobrecargas y el autoboxing, descrito en Reflection from Scala - Heaven and Hell.

+2

Teniendo en cuenta que la función experimental en mi respuesta no hizo 2.8.0, sería mejor si otra respuesta fue marcado como aceptado. –

+0

si tengo clase con parámetros para una clase como clase MailServerice (emailIds: cadena) ¿es posible invocar dinámicamente en tiempo de ejecución? – ashK

Respuesta

57

Hay una forma más fácil de invocar el método reflexivamente sin recurrir a Java llamando métodos de reflexión: use la tipificación estructural.

Simplemente copie la referencia del objeto a un Tipo estructural que tenga la firma de método necesaria y luego invoque el método: no es necesario reflexionar (por supuesto, Scala está haciendo una reflexión debajo pero no necesitamos hacerlo).

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

Tipo estructural no me ayudará si no conozco el nombre del método en el momento de la compilación. –

+7

usando un alias de tipo para dar un tipo de nombre estructural a menudo mejora la legibilidad de este truco. –

+1

Esto solo responde a una parte de la pregunta. ¿Hay una forma céntrica de Scala de realizar el Método method = class.getMethod ("hello", null) ;? –

6

El instanciation parte podría utilizar el Manifest: ver esto 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) 

Así que la solución segura cierto tipo de estaría usando una fábrica.


Nota: como se indica en this thread, Manifiesto está aquí para quedarse, pero por ahora es "único uso es dar acceso a la supresión del tipo como una instancia de la clase."

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


entonces se puede obtener un método mediante la reflexión:

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

e invocarlo

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

Las respuestas de VonCWalter Chang y son bastante buenos, por lo que sólo se complementan con una característica experimental Scala 2.8. De hecho, ni siquiera me molestaré en disfrazarlo, solo copiaré el scaladoc.

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

Esto es algo de lo que tenía en mente desde el artículo del blog vinculado. Otra pieza del rompecabezas es el nombre de servicio de manipulación. –

+3

Lamentablemente, Invocation no terminó haciéndolo: https://lampsvn.epfl.ch/trac/scala/changeset/19695 –

3

En caso de que tenga que invocar un método de un objeto Scala 2.10 (no clase) y tiene los nombres del método y el objeto como String s, puede hacerlo así:

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

Esto imprime 5 a la salida estándar. Más detalles en Scala Documentation.

+1

¿Qué pasa con una clase, no un objeto? – matanster

4

La elaboración de la respuesta de @ nedim, aquí es una base para una respuesta completa, principal diferencia es que aquí abajo instanciar clases ingenuos. Este código no maneja el caso de múltiples constructores, y de ninguna manera es una respuesta completa.

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

Aquí es una build.sbt que compilarlo:

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

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

Cuestiones relacionadas