Dans Scala, quelle est la meilleure façon d'instancier dynamiquement un objet et d'invoquer une méthode en utilisant la réflexion?
Je voudrais faire l'équivalent Scala du code Java Java:
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 classe et le nom de méthode sont transmis dynamiquement. Le mécanisme Java Java ci-dessus pourrait probablement être utilisé pour Foo
et hello()
, mais les types Scala ne correspondent pas un à un avec celui de Java. Par exemple, une classe peut être déclarée implicitement pour un objet singleton. Aussi Scala permet à toutes sortes de symboles d'être son nom. Les deux sont résolus par voir le nom. Voir Interop entre Java et Scala .
Un autre problème semble être la correspondance des paramètres en résolvant les surcharges et la mise en boîte automatique, décrites dans Réflexion de Scala - Heaven and Hell .
Il existe un moyen plus simple d'appeler la méthode de manière réfléchie sans avoir à appeler Java méthodes de réflexion: utilisez Structural Typing.
Il suffit de lancer la référence d'objet à un type structurel qui a la signature de méthode nécessaire puis d'appeler la méthode: aucune réflexion nécessaire (bien sûr, Scala fait 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 de VonC et Walter Chang sont assez bonnes, donc je vais juste compléter avec une Scala 2.8 Fonction 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 l'invocation réfléchie. Exemple d'utilisation:
class Obj { private def foo(x: Int, y: String): Long = x + y.length }
Vous pouvez l'appeler de manière réfléchie de deux manières:
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 la méthode oo et ne donnez pas suffisamment d'aide au déducteur de type, il en déduira probablement Nothing, ce qui entraînera une exception ClassCastException.
Auteur Paul Phillips
Dans le cas où vous devez invoquer une méthode d'un objet Scala 2.10 (pas une classe) et que 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
vers la sortie standard. Plus de détails dans Documentation Scala .
La partie instanciation pourrait utiliser le manifeste : voir ceci réponse SO
fonction expérimentale dans Scala appelés 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 sans argument 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)
Ainsi, la véritable solution sûre de type serait d'utiliser une usine.
Remarque: comme indiqué dans ce fil , le manifeste est là pour rester, mais est pour l'instant "la seule utilisation est de donner accès à l'effacement du type en tant qu'instance de classe".
La seule chose que les manifestes vous donnent maintenant est l'effacement du type statique d'un paramètre sur le site d'appel (contrairement à
getClass
qui vous donne l'effacement du dynamique type).
Vous pouvez alors obtenir une méthode par 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
À partir de la réponse de @ nedim, voici un base pour une réponse complète, la principale différence étant ici-bas, nous instancions des classes naïves. Ce code ne gère pas le cas de plusieurs constructeurs et n'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 une 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"
)
)