55

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

我想做以下Java代码的Scala等价物:

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

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

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

4

5 回答 5

72

有一种更简单的方法来反射调用方法,而无需调用 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"
  }
}
于 2009-09-24T07:15:07.193 回答
12

VonCWalter Chang的回答非常好,所以我将仅补充一个 Scala 2.8 实验特性。事实上,我什至不会费心去修饰它,我只会复制 scaladoc。

object Invocation
  extends AnyRef

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

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

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

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.

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

作者保罗菲利普斯

于 2009-09-24T12:54:09.293 回答
7

实例化部分可以使用Manifest:看到这个SO answer

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

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

使用此版本,您仍然可以编写

class Foo
val t = new Test[Foo]

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

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

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


注意:正如该线程中所述,Manifest 将继续存在,但目前“仅用于将类型的擦除作为 Class 实例提供访问权限”。

清单现在唯一给您的是在调用站点上擦除参数的静态类型(与之相反getClass,给您动态类型的擦除)。


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

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 
于 2009-09-24T06:36:38.670 回答
7

如果您需要调用 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 文档中的更多详细信息。

于 2015-09-21T15:54:22.447 回答
5

从@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"
    )
  )
于 2015-12-11T15:16:47.727 回答