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 (не класс), и у вас есть имена метода и объекта как Strings, Вы можете сделать это так:

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