Scala: How to dynamically instantiate an object and call a method using reflection?

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

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

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

In the above code, the class name and method name are passed dynamically. The above Java mechanism can probably be used for Foo and hello() , but the Scala types are not the same as Java. For example, a class may be declared implicitly for a singleton object. In addition, the Scala method allows you to use all kinds of characters. Both are resolved by changing the name. See Interaction between Java and Scala .

Another problem is the comparison of parameters by resolving overloads and autoboxing described in Reflection from Scala - Heaven and Hell .

+43
reflection scala name-mangling
Sep 24 '09 at 5:51
source share
5 answers

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

Just assign an object reference to a Structural type that has the required method signature, then call the method: no reflection is required (of course, Scala does the reflection below, but we don’t need to).

 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" } } 
+55
Sep 24 '09 at 7:15
source share

The answers of VonC and Walter Chang are not bad, so I'll just add Scala 2.8 with one. The experimental feature. In fact, I won’t even get dressed, I just copy the scaladoc.

 object Invocation extends AnyRef 

More convenient syntax for reflexive invocation. Usage example:

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

You can call it reflexively in 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 will help enough, it will probably output nothing, which will throw a ClassCastException.

Posted by Paul Phillips

+12
Sep 24 '09 at 12:54
source share

The instanciation part can use the manifest : see this SO>

Scala's experimental feature is called manifests, which are a way around the Java restriction regarding type erasure.

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

In this version you are still writing

 class Foo val t = new Test[Foo] 

However, if there is no no-arg constructor, you get an exception at run time 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) 

Thus, a true secure solution will use Factory.




Note: as indicated in this thread , Manifest is here to stay, but for now "use only access to erase the type as an instance of the class."

The only thing that appears now is erasing the static type of the parameter on the call site (unlike getClass , which gives you erasing the dynamic type).




Then you can get the method through reflection:

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

and call him

 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
Sep 24 '09 at 6:36
source share

Based on @nedim's answer, here is the basis for a complete answer, The main difference is below, we create naive classes. This code does not handle the case with multiple constructors and is by no means a complete answer.

 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 build.sbt , which would compile it:

 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
Dec 11 '15 at 15:16
source share

If you need to call the Scala 2.10 object method (and not the class), and you have the method and object names like String s, you can do this as follows:

 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) } } 

Prints 5 to standard output. Further details in the Scala Documentation .

+3
Sep 21 '15 at 15:54
source share



All Articles