views:

1744

answers:

4

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

I would like to do Scala-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, both the class name and the method name are passed in dynamically. The above Java mechanism could probably be used for Foo and hello(), but the Scala types don't match one-to-one with that of Java. For example, a class may be declared implicitly for a singleton object. Also Scala method allows all sorts of symbols to be its name. Both are resolved by name mangling. See Interop Between Java and Scala.

Another issue seems to be the matching of parameters by resolving overloads and autoboxing, described in Reflection from Scala - Heaven and Hell.

+2  A: 

The instanciation part could use the Manifest: see this SO answer

experimental feature in Scala called manifests which are a way to get around a Java constraint regarding type erasure

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

With this version you still write

class Foo
val t = new Test[Foo]

However, if there's no no-arg constructor available you get a runtime exception 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)

So the true type safe solution would be using a Factory.


Note: as stated in this thread, Manifest is here to stay, but is for now "only use is to give access to the erasure of the type as a Class instance."

The only thing manifests give you now is the erasure of the static type of a parameter at the call site (contrary to getClass which give you the erasure of the dynamic type).


You can then get a method through reflection:

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

and invoke it

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
VonC
+4  A: 

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

Just cast the object reference to a Structural Type which has the necessary method signature then call the method: no reflection necessary (of course, Scala is doing reflection underneath but we don't need to do it).

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"
  }
}
Walter Chang
Nice use of Structural Type there. +1
VonC
Structural Type won't help me if I don't know the method name at the compile time.
eed3si9n
using a type alias to give the structural type a name often improves the readability of this trick.
Seth Tisue
+4  A: 

The answers by VonC and Walter Chang are quite good, so I'll just complement with one Scala 2.8 Experimental feature. In fact, I won't even bother to dress it up, I'll just copy the scaladoc.

object Invocation
  extends AnyRef

A more convenient syntax for reflective invocation. Example usage:

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

You can call it reflectively 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 enough help, it will most likely infer Nothing, which will result in a ClassCastException.

Author Paul Phillips

Daniel
Good to know. I need to play more with 2.8. +1
VonC
This is kind of what I had in mind from the linked blog article. Another piece of the puzzle is the name mangling service.
eed3si9n
Sadly, Invocation didn't end up making it:https://lampsvn.epfl.ch/trac/scala/changeset/19695
Adam Rabung
A: 

How come this doen't work?

$ ./scala
Welcome to Scala version 2.8.0.r21322-b20100402204851 (Java HotSpot(TM) Server VM, Java 1.6.0_20).
Type in expressions to have them evaluated.
Type :help for more information.

scala> class Foo
defined class Foo

scala> 

scala> Class.forName("Foo")
java.lang.ClassNotFoundException: Foo
        at scala.tools.nsc.interpreter.AbstractFileClassLoader.onull$1(AbstractFileClassLoader.scala:22)
        at scala.tools.nsc.interpreter.AbstractFileClassLoader.getBytesForClass(AbstractFileClassLoader.scala:29)
Andreas
eed3si9n