web-dev-qa-db-ja.com

Scala:リフレクションを使用してオブジェクトを動的にインスタンス化し、メソッドを呼び出すにはどうすればよいですか?

Scalaでは、オブジェクトを動的にインスタンス化し、リフレクションを使用してメソッドを呼び出す最良の方法は何ですか?

次のJavaコードと同等のScalaを実行したい:

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

上記のコードでは、クラス名とメソッド名の両方が動的に渡されます。上記のJavaメカニズムは、おそらくFooおよびhello()に使用できますが、Scalaタイプは一致しませんたとえば、クラスはシングルトンオブジェクトに対して暗黙的に宣言されます。また、Scalaメソッドは、あらゆる種類のシンボルをその名前にすることができます。両方ともによって解決されます。 name mangling。 Interop Between Java and Scala を参照してください。

別の問題は、 Reflection from Scala-Heaven and Hell で説明されているように、オーバーロードとオートボクシングを解決することによるパラメーターのマッチングのようです。

50
Eugene Yokota

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"
  }
}
66
Walter Chang

VonC および Walter Chang による回答は非常に優れているため、1つのScala 2.8実験機能で補完します。実際には、私はそれを着飾ることさえしません。私はスカラドックをコピーするだけです。

object Invocation
  extends AnyRef

リフレクション呼び出しのより便利な構文。使用例:

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

次の2つの方法のいずれかを反映して呼び出すことができます。

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が発生します。

著者ポール・フィリップス

12

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 にあります。

7
nedim

インスタンス化部分couldManifest を使用します。これを参照してください そう答え

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)

したがって、真のタイプセーフソリューションはファクトリを使用することになります。


注: このスレッド に記載されているように、マニフェストはここにありますが、現時点では「クラスインスタンスとしての型の消去へのアクセスを許可することのみ」です。

マニフェストが今あなたに与える唯一のものは、呼び出しサイトでのパラメータのstaticタイプの消去です(getClassとは反対に、dynamictype)。


その後、リフレクションを介してメソッドを取得できます。

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 
6
VonC

@nedimの答えから仕上げて、ここにbasisがあり、完全な答えが得られます。主な違いは以下のとおり、単純なクラスをインスタンス化します。このコードは、複数のコンストラクターの場合を処理せず、完全な答えではありません。

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