Scala: jak dynamicznie utworzyć instancję obiektu i wywołać metodę za pomocą reflection?
W Scali, jaki jest najlepszy sposób dynamicznego tworzenia instancji obiektu i wywoływania metody za pomocą odbicia?
Chciałbym zrobić Scala-odpowiednik następującego kodu Javy:
Class class = Class.forName("Foo");
Object foo = class.newInstance();
Method method = class.getMethod("hello", null);
method.invoke(foo, null);
W powyższym kodzie zarówno nazwa klasy, jak i nazwa metody są przekazywane dynamicznie. Powyższy Mechanizm Java może być prawdopodobnie użyty dla Foo
i hello()
, ale typy Scali nie pasują jeden do jednego z tym Java. Na przykład, klasa może być zadeklarowana domyślnie dla obiektu singleton. Również metoda Scala pozwala na nazwanie WSZYSTKICH rodzajów symboli. Oba są rozwiązywane przez namaglowanie nazwy. Zobacz interakcję pomiędzy Javą a scalą .
5 answers
Istnieje łatwiejszy sposób wywoływania metody refleksyjnej bez uciekania się do wywoływania metod refleksyjnych Javy: użyj typowania strukturalnego.
Wystarczy rzucić odniesienie do obiektu do typu strukturalnego, który ma wymaganą sygnaturę metody, a następnie wywołać metodę: no reflection necessary (oczywiście Scala robi odbicie pod spodem, ale nie musimy tego robić).
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"
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-24 07:15:07
Odpowiedzi VonC i Walter Chang są całkiem dobre, więc uzupełnię je o jedną eksperymentalną funkcję Scali 2.8. W zasadzie, nawet nie będę się starał go ubrać, tylko skopiuję scaladoc.
object Invocation
extends AnyRef
Wygodniejsza składnia refleksu inwokacja. Przykład użycia:
class Obj { private def foo(x: Int, y: String): Long = x + y.length }
Można to nazwać refleksyjnie jednym z dwa sposoby:
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.
Jeśli zadzwonisz do oo sposób i nie podawaj typu inferencer wystarczy pomocy, to będzie najbardziej prawdopodobnie nic nie wnioskować, co będzie wynik w ClassCastException.Autor Paul Phillips
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:13
Część instancjowa może użyć Manifest: Zobacz to więc odpowiedz
W Scali istnieją dwa rodzaje manifestów, które są sposobem na obejście ograniczeń Javy dotyczących usuwania typów.]}
class Test[T](implicit m : Manifest[T]) {
val testVal = m.erasure.newInstance().asInstanceOf[T]
}
Z tą wersją nadal piszesz
class Foo
val t = new Test[Foo]
Jeśli jednak nie jest dostępny konstruktor no-arg, otrzymasz wyjątek runtime zamiast typu statycznego błąd
scala> new Test[Set[String]]
java.lang.InstantiationException: scala.collection.immutable.Set
at java.lang.Class.newInstance0(Class.java:340)
Więc prawdziwym bezpiecznym rozwiązaniem byłoby użycie fabryki.
Uwaga: jak stwierdzono w ten wątek, Manifest jest tutaj, aby pozostać, ale jest na razie " jedynym użyciem jest dać dostęp do kasowania typu jako instancji klasy."
Jedyną rzeczą, która daje Ci teraz, jest usunięcie typu statycznego parametru w miejscu wywołania (w przeciwieństwie do
getClass
, które dają usunięcietypu dynamicznego ).
Można wtedy uzyskać metodę poprzez odbicie:
classOf[ClassName].getMethod("main", classOf[Array[String]])
I wywołaj go
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:34
Jeśli chcesz wywołać metodę obiektu Scala 2.10 (Nie klasy) i masz nazwy metody i obiektu jako String
s, możesz to zrobić w następujący sposób:
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)
}
}
Wyświetla 5
na standardowe wyjście.
Więcej szczegółów w dokumentacji Scali .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-21 16:02:46
Praca z odpowiedzi @nedim, oto podstawa dla pełnej odpowiedzi, główną różnicą jest to, że poniżej tworzymy naiwne klasy. Ten kod nie obsługuje przypadku wielu konstruktorów i w żadnym wypadku nie jest pełną odpowiedzią.
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", "")
}
Oto build.sbt
, który by ją skompilował:
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"
)
)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-11 16:09:54