【问题标题】:Scala: How do I dynamically instantiate an object and invoke a method using reflection?Scala:如何动态实例化对象并使用反射调用方法?
【发布时间】:2010-11-30 23:53:14
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 鉴于我的答案中的实验性功能没有使 2.8.0,如果另一个答案被标记为接受会更好。
  • 如果我的类带有类 MailServerice(emailIds : string) 之类的参数,是否可以在运行时动态调用?

标签: reflection scala name-mangling


【解决方案1】:

有一种更简单的方法来反射调用方法,而无需调用 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"
  }
}

【讨论】:

  • 如果我在编译时不知道方法名称,结构类型对我没有帮助。
  • 使用类型别名为结构类型命名通常会提高此技巧的可读性。
  • 这仅回答了部分问题。是否有以 Scala 为中心的方式来执行 Method method = class.getMethod("hello", null);?
  • 在这个例子中,Foo 没有使用构造函数参数,但是如果有,你将如何在newInstance 上传递它们?
  • 在 scala repl 中执行这个我得到java.lang.NoClassDefFoundError: Foo (wrong name: Foo)
【解决方案2】:

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 方法并且不给出类型 inferencer 足够的帮助,它会最 可能会推断无,这将 导致 ClassCastException。

作者保罗·菲利普斯

【讨论】:

【解决方案3】:

实例化部分可以使用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)

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


注意:正如this thread 中所述,Manifest 将继续存在,但目前“仅用于授予对类型作为 Class 实例的擦除的访问权限。”

清单现在唯一给你的是在调用站点删除 static 类型的参数(与 getClass 相反,它可以让你删除 dynamic em> 类型)。


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

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 

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 类而不是对象呢?
    【解决方案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"
        )
      )
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多