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 .

46
demandé sur Eugene Yokota 2009-09-24 09:51:41

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"
  }
}
61
répondu Walter Chang 2009-09-24 07:15:07

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

12
répondu Daniel C. Sobral 2017-05-23 12:26:13

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 
6
répondu VonC 2017-05-23 10:31:34

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 .

5
répondu nedim 2015-09-21 16:02:46

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"
    )
  )
4
répondu matanster 2015-12-11 16:09:54