Scala: comment instancier dynamiquement un objet et invoquer une méthode utilisant la réflexion?
en Scala, Quelle est la meilleure façon d'instancier dynamiquement un objet et d'invoquer une méthode utilisant la réflexion?
je voudrais faire L'équivalent Scala du code Java suivant:
Class class = Class.forName("Foo");
Object foo = class.newInstance();
Method method = class.getMethod("hello", null);
method.invoke(foo, null);
dans le code ci-dessus, le nom de la classe et le nom de la méthode sont passés dynamiquement. Le mécanisme Java ci-dessus pourrait probablement être utilisé pour Foo
et hello()
, mais les types Scala ne correspondent pas à celui de Java. Exemple, une classe peut être déclarée implicitement pour un objet singleton. Aussi la méthode Scala permet à toutes sortes de symboles d'être son nom. Les deux sont résolus par la mutilation de nom. Voir Interop Entre Java et Scala .
un autre problème semble être l'appariement des paramètres en réglant les surcharges et l'autoboxing, décrit dans Reflection from Scala - Heaven and Hell .
5 réponses
il est plus facile d'invoquer la méthode de façon réflective sans avoir recours aux méthodes de réflexion Java: utiliser le typage structurel.
vient de lancer la référence de L'objet à un type structurel qui a la signature de la méthode nécessaire puis appeler la méthode: pas de réflexion nécessaire (bien sûr, Scala fait de la réflexion en dessous mais nous n'avons pas besoin de le faire).
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"
}
}
Les réponses par VonC et Walter Chang sont assez bon, donc je vais juste compléter avec un Scala 2.8 fonctionnalité Expérimentale. En fait, je ne vais même pas prendre la peine de l'habiller, je vais juste copier le scaladoc.
object Invocation
extends AnyRef
une syntaxe plus pratique pour la réflexion invocation. Exemple d'utilisation:
class Obj { private def foo(x: Int, y: String): Long = x + y.length }
vous pouvez l'appeler réflectivement l'un des deux voies:
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.
si vous appelez l'oo méthode et ne pas donner le type inferencer assez d'aide, il sera plus probablement Rien inférer, qui suite à une ClassCastException.
Auteur Paul Phillips
L'instanciation de la partie pourrait utiliser le Manifeste : voir AFIN de répondre à
dispositif expérimental en Scala appelé manifestes qui sont un moyen de contourner une contrainte Java concernant l'effacement de type
class Test[T](implicit m : Manifest[T]) {
val testVal = m.erasure.newInstance().asInstanceOf[T]
}
avec cette version vous écrivez toujours
class Foo
val t = new Test[Foo]
cependant, s'il n'y a pas de constructeur no-arg disponible, vous obtenez une exception d'exécution au lieu d'une erreur de type statique
scala> new Test[Set[String]]
java.lang.InstantiationException: scala.collection.immutable.Set
at java.lang.Class.newInstance0(Class.java:340)
donc la vraie solution de sécurité de type serait d'utiliser une usine.
Note: Comme indiqué dans ce fil , manifeste est ici pour rester, mais est pour l'instant "seule utilisation est de donner accès à l'effacement du type comme une classe instance."
la seule chose que les manifestes vous donnent maintenant est l'effacement du type statique d'un paramètre au site d'appel (contrairement à
getClass
qui vous donne l'effacement du type dynamique ).
vous pouvez alors obtenir une méthode par la réflexion:
classOf[ClassName].getMethod("main", classOf[Array[String]])
et l'invoquer
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
dans le cas où vous devez invoquer une méthode D'un objet Scala 2.10 (pas de classe) et vous avez les noms de la méthode et de l'objet comme String
s, vous pouvez le faire comme ceci:
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)
}
}
cela imprime 5
à la sortie standard.
Plus de détails dans documentation Scala .
en partant de la réponse de @nedim, voici une base pour une réponse complète, principale différence étant ici-bas, nous instancier naïf classes. Ce code ne gère pas le cas de plusieurs constructeurs, et est en aucun cas une réponse complète.
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", "")
}
voici un build.sbt
qui le compilerait:
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"
)
)