Scala: Come posso dinamicamente un'istanza di un oggetto e di invocare un metodo utilizzando la riflessione?

StackOverflow https://stackoverflow.com/questions/1469958

  •  16-09-2019
  •  | 
  •  

Domanda

In Scala, qual è il modo migliore per creare un'istanza in modo dinamico un oggetto e richiamare un metodo che utilizza riflessione?

Vorrei fare Scala-equivalente del seguente codice Java:

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

Nel codice di cui sopra, sia il nome della classe e il nome del metodo vengono passati in modo dinamico. Il meccanismo di Java sopra potrebbe probabilmente essere utilizzato per Foo e hello(), ma i tipi Scala non corrispondono uno-a-uno con quella di Java. Ad esempio, una classe può essere dichiarata implicitamente per un oggetto Singleton. Anche il metodo Scala consente a tutti i tipi di simboli per la sua denominazione. Entrambi sono risolti per nome mangling. Vedere interoperabilità tra Java e Scala .

Un altro problema sembra essere l'abbinamento dei parametri risolvendo sovraccarichi e autoboxing, descritti in Riflessione da Scala - Heaven and hell .

È stato utile?

Soluzione

C'è un modo più semplice per richiamare il metodo riflessivo, senza ricorrere a chiamare i metodi di riflessione di Java:. Utilizzare Typing strutturale

Basta lanciare il riferimento all'oggetto a un tipo di struttura che ha la firma del metodo necessario quindi chiamare il metodo:. Nessuna riflessione necessaria (naturalmente, Scala sta facendo riflessione sotto, ma non abbiamo bisogno di farlo)

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

Altri suggerimenti

Le risposte di VonC e Walter Chang sono abbastanza buoni, quindi mi limiterò a completare con una caratteristica sperimentale Scala 2.8. In realtà, io non voglio nemmeno la briga di vestire in su, mi limiterò a copiare lo scaladoc.

object Invocation
  extends AnyRef
  

Un più conveniente sintassi per riflettente   invocazione. Esempio di utilizzo:

class Obj { private def foo(x: Int, y: String): Long = x + y.length }
  

Si può chiamare uno dei reflectively   due modi:

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.
  

Se si chiama il oo   metodo e non dare il tipo   inferencer sufficiente aiuto, lo farà più   probabilmente dedurre Niente, che sarà   tradursi in una ClassCastException.

     

Autore Paul Phillips

Nel caso in cui avete bisogno di invocare un metodo di un oggetto Scala 2.10 (non classe) e si ha il nome del metodo e oggetto come Strings, si può fare in questo modo:

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

Questo stampa 5 sullo standard output. Ulteriori dettagli in Scala Documentazione .

La parte instanciation potrebbero usare il manifesto : vedere questo SO rispondere

  

funzione sperimentale a Scala chiamato manifesti che sono un modo per aggirare un vincolo di Java per quanto riguarda la cancellazione di tipo

 class Test[T](implicit m : Manifest[T]) {
   val testVal = m.erasure.newInstance().asInstanceOf[T]
 }
  

Con questa versione è ancora scrivere

class Foo
val t = new Test[Foo]
  

Tuttavia, se non c'è a disposizione costruttore no-arg si ottiene un'eccezione di runtime invece di un errore di tipo statico

scala> new Test[Set[String]] 
java.lang.InstantiationException: scala.collection.immutable.Set
at java.lang.Class.newInstance0(Class.java:340)

Quindi la soluzione sicura vero tipo sarebbe utilizzando una fabbrica.


Nota: come indicato nel questa discussione , manifesto è qui per restare , ma è per ora "solo per uso è quello di dare accesso alla cancellazione del tipo come un'istanza di classe".

  

L'unica manifesta cosa offrirti ora è la cancellazione del static tipo di un parametro al luogo di chiamata (contrariamente a getClass che vi danno la cancellazione del dinamica genere).


È possibile quindi ottenere un metodo attraverso la riflessione:

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 = A@1f854bd

scala>a.getClass.getMethod(decode("foo_="),
classOf[Boolean]).invoke(a, java.lang.Boolean.TRUE)
res15: java.lang.Object = bar 

Lavorare su da @ di Nedim risposta, qui è un base per una risposta completa, differenza principale è qui sotto istanziamo classi ingenui. Questo codice non gestisce il caso di più costruttori, e non è affatto una risposta 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", "")
}

Ecco un build.sbt che 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"
    )
  )
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top