Scala:如何动态实例化对象并使用反射调用方法?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1469958/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 01:39:32  来源:igfitidea点击:

Scala: How do I dynamically instantiate an object and invoke a method using reflection?

reflectionscalaname-mangling

提问by Eugene Yokota

In Scala, what's the best way to dynamically instantiate an object and invoke a method using reflection?

在 Scala 中,动态实例化对象并使用反射调用方法的最佳方法是什么?

I would like to do Scala-equivalent of the following Java code:

我想做以下 Java 代码的 Scala 等效项:

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

In the above code, both the class name and the method name are passed in dynamically. The above Java mechanism could probably be used for Fooand hello(), but the Scala types don't match one-to-one with that of Java. For example, a class may be declared implicitly for a singleton object. Also Scala method allows all sorts of symbols to be its name. Both are resolved by name mangling. See Interop Between Java and Scala.

上面代码中,类名和方法名都是动态传入的。上面的 Java 机制可能可以用于Fooand hello(),但是 Scala 类型与 Java 的类型不一一匹配。例如,可以为单例对象隐式声明一个类。Scala 方法还允许使用各种符号作为其名称。两者都通过名称修改来解决。请参阅Java 和 Scala 之间的互操作

Another issue seems to be the matching of parameters by resolving overloads and autoboxing, described in Reflection from Scala - Heaven and Hell.

另一个问题似乎是通过解决重载和自动装箱来匹配参数,这在Scala 的 Reflection - Heaven and Hell 中有所描述。

回答by Walter Chang

There is an easier way to invoke method reflectively without resorting to calling Java reflection methods: use Structural Typing.

有一种更简单的方法可以反射调用方法,而无需调用 Java 反射方法:使用结构类型。

Just cast the object reference to a Structural Type which has the necessary method signature then call the method: no reflection necessary (of course, Scala is doing reflection underneath but we don't need to do it).

只需将对象引用转换为具有必要方法签名的结构类型,然后调用该方法:不需要反射(当然,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"
  }
}

回答by Daniel C. Sobral

The answers by VonCand Walter Changare quite good, so I'll just complement with one Scala 2.8 Experimental feature. In fact, I won't even bother to dress it up, I'll just copy the scaladoc.

VonCWalter Chang的回答非常好,所以我将补充一个 Scala 2.8 实验功能。事实上,我什至懒得修饰它,我只会复制scaladoc。

object Invocation
  extends AnyRef

A more convenient syntax for reflective invocation. Example usage:

一种更方便的反射调用语法。用法示例:

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

You can call it reflectively one of two ways:

您可以通过以下两种方式之一反射性地调用它:

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.

If you call the oo method and do not give the type inferencer enough help, it will most likely infer Nothing, which will result in a ClassCastException.

Author Paul Phillips

如果您调用 oo 方法并且没有给类型推断器足够的帮助,它很可能会推断出 Nothing,这将导致 ClassCastException。

作者保罗菲利普斯

回答by nedim

In case you need to invoke a method of a Scala 2.10 object (not class) and you have the names of the method and object as Strings, you can do it like this:

如果您需要调用 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)
  }
}

This prints 5to standard output. Further details in Scala Documentation.

这将打印5到标准输出。Scala 文档中的更多详细信息。

回答by VonC

The instanciation part coulduse the Manifest: see this SO answer

实例化部分可以使用清单:请参阅此SO 答案

experimental feature in Scala called manifests which are a way to get around a Java constraint regarding type erasure

Scala 中的实验性功能称为清单,这是一种绕过 Java 类型擦除约束的方法

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

With this version you still write

用这个版本你仍然写

class Foo
val t = new Test[Foo]

However, if there's no no-arg constructor available you get a runtime exception instead of a static type error

但是,如果没有可用的无参数构造函数,则会得到运行时异常而不是静态类型错误

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

So the true type safe solution would be using a Factory.

所以真正的类型安全解决方案是使用工厂。



Note: as stated in this thread, Manifest is here to stay, but is for now "only use is to give access to the erasure of the type as a Class instance."

注意:如该线程中所述,Manifest 将保留,但目前“仅用于将类型的擦除作为 Class 实例进行访问”。

The only thing manifests give you now is the erasure of the statictype of a parameter at the call site (contrary to getClasswhich give you the erasure of the dynamictype).

现在清单给你的唯一一件事是在调用站点擦除参数的静态类型(相反,getClass它给你动态类型的擦除)。



You can then get a method through reflection:

然后就可以通过反射得到一个方法:

classOf[ClassName].getMethod("main", classOf[Array[String]]) 

and invoke it

并调用它

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 

回答by matanster

Working up from @nedim's answer, here is a basisfor a full answer, main difference being here below we instantiate naive classes. This code does not handle the case of multiple constructors, and is by no means a full answer.

从@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", "")
}

Here is a build.sbtthat would compile it:

这是一个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"
    )
  )