55

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 の型は Java の型と 1 対 1 で一致しません。たとえば、クラスはシングルトン オブジェクトに対して暗黙的に宣言される場合があります。また、Scala メソッドでは、あらゆる種類のシンボルをその名前にすることができます。両方とも、名前マングリングによって解決されます。Java と Scala の間の相互運用を参照してください。

もう 1 つの問題は、Scala からの反射 - 天国と地獄で説明されている、オーバーロードとオートボクシングの解決によるパラメーターの一致のようです。

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 の実験的な機能を 1 つ追加して補足します。実際、私はそれをドレスアップすることさえ気にせず、scaladoc をコピーするだけです。

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

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

于 2009-09-24T12:54:09.293 に答える
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 Documentationを参照してください。

于 2015-09-21T15:54:22.447 に答える
7

インスタンス化部分はマニフェストを使用できます:このSOの回答を参照してください

マニフェストと呼ばれる 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)

したがって、真のタイプ セーフ ソリューションは Factory を使用することです。


注:このスレッドで述べられているように、マニフェストはここに残りますが、現時点では「クラス インスタンスとしての型の消去へのアクセスを提供することだけが使用されます」。

マニフェストが提供する唯一のものは、呼び出しサイトでのパラメーターの静的型の消去です (動的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 に答える