Scala: как динамически создавать экземпляр объекта и вызывать метод с помощью отражения?
в Scala, каков наилучший способ динамически создавать экземпляр объекта и вызывать метод с помощью отражения?
Я хотел бы сделать Scala-эквивалент следующего кода Java:
Class class = Class.forName("Foo");
Object foo = class.newInstance();
Method method = class.getMethod("hello", null);
method.invoke(foo, null);
В приведенном выше коде имя класса и имя метода передаются динамически. Вышеупомянутый механизм Java, вероятно, может быть использован для Foo
и hello()
, но типы Scala не совпадают один к одному с типом Java. Например, класс может быть объявлен неявно для одноэлементного объекта. Также метод Scala позволяет всевозможным символам быть его именем. Оба разрешены по имени mangling. См.взаимодействие между Java и Scala.
другой проблемой, по-видимому, является соответствие параметров путем разрешения перегрузок и автобоксинга, описанных в отражение от скалы-Рай и ад.
5 ответов
существует более простой способ вызвать метод рефлексивно, не прибегая к вызову методов отражения Java: используйте структурный типирование.
просто приведите ссылку на объект к структурному типу, который имеет необходимую сигнатуру метода, затем вызовите метод: нет необходимости в отражении (конечно, Scala делает отражение внизу, но нам не нужно это делать).
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"
}
}
ответы VonC и Уолтер Чанг довольно хороши, поэтому я просто дополню одну экспериментальную функцию Scala 2.8. На самом деле, я даже не буду его наряжать, я просто скопирую scaladoc.
object Invocation
extends AnyRef
более удобный синтаксис для отражения вызов. Пример использования:
class Obj { private def foo(x: Int, y: String): Long = x + y.length }
вы можете назвать его рефлексивно одним из два способа:--8-->
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.
Если вы называете ОО метод и не дают тип inferencer достаточно помогает, это будет большинство скорее всего ничего не получится, что будет результат в ClassCastException.
Автор Пол Филлипс
часть instanciation мог бы использовать Манифест: увидеть это так что ответ
экспериментальная функция в Scala называется манифестами, которые являются способом обойти ограничение Java относительно стирания типа
class Test[T](implicit m : Manifest[T]) {
val testVal = m.erasure.newInstance().asInstanceOf[T]
}
С этой версией вы все еще пишете
class Foo
val t = new Test[Foo]
однако, если нет конструктора no-arg, вы получаете среду выполнения исключение вместо статического типа error
scala> new Test[Set[String]]
java.lang.InstantiationException: scala.collection.immutable.Set
at java.lang.Class.newInstance0(Class.java:340)
таким образом, истинное безопасное решение будет использовать завод.
Примечание: как сказано в этой теме, Манифест здесь, чтобы остаться, но на данный момент "только использование-дать доступ к стиранию типа как экземпляра класса."
единственное, что манифесты дают вам сейчас, это стирание static тип параметра на сайте вызова (в отличие от
getClass
, которые дают вам стиранием динамический type).
затем вы можете получить метод через отражение:
classOf[ClassName].getMethod("main", classOf[Array[String]])
и вызывать его
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
в случае, если вам нужно вызвать метод объекта Scala 2.10 (не класс), и у вас есть имена метода и объекта как String
s, Вы можете сделать это так:
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)
}
}
это выводит 5
в стандартный вывод.
Более подробная информация в Документация Scala.
работа с ответом @nedim, вот основа для полного ответа, основное отличие здесь ниже, мы создаем экземпляры наивных классов. Этот код не обрабатывает случай нескольких конструкторов и ни в коем случае не является полным ответом.
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", "")
}
здесь build.sbt
что бы скомпилировать его:
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"
)
)