Scala: Come posso dinamicamente un'istanza di un oggetto e di invocare un metodo utilizzando la riflessione?
-
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 .
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 String
s, 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"
)
)