web-dev-qa-db-fra.com

Scala: Comment instancier dynamiquement un objet et invoquer une méthode à l'aide de la réflexion?

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 .

50
Eugene Yokota

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"
  }
}
66
Walter Chang

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

12
Daniel C. Sobral

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 Strings, 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 .

7
nedim

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 
6
VonC

À 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"
    )
  )
4
matanster