Wrong number of arguments invoking a Scala constructor using reflection
Asked Answered
S

1

7

I'm trying to invoke the newInstance method of a constructor of a Scala class (case class or usual class, both are affected).

However, I'm running into a IllegalArgumentException with the hint wrong number of arguments.

Consider the following:

case class Vec2(x: Float, y: Float)

object TestApp {
  def main(args: Array[String]) {
    //after some research I found the last constructor always to be the default
    val ctor = classOf[Vec2].getConstructors.last

    println("ctor = " + ctor)
    println("takes parameters: " + ctor.getParameterTypes.length)

    val params = new Array[Float](2)

    params.update(0, 1.0f)
    params.update(1, -1.0f)

    println("num parameters: " + params.length)

    println("trying to create new instance...")
    try {
      val x = ctor.newInstance(params)
      println("new instance: " + x)
    }
    catch {
      case ex => ex.printStackTrace
    }
  }

The output is as follows:

ctor = public pd.test.Vec2(float,float)
takes parameters: 2
num parameters: 2
trying to create new instance...
java.lang.IllegalArgumentException: wrong number of arguments
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    at pd.test.TestApp$.main(TestApp.scala:60)
    at pd.test.TestApp.main(TestApp.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:115)

I have experienced something like this in Java once. In that case the class I was trying to instantiate was an inner class of another class, so Java expected an implicit additional parameter, which was either the Class object of the enclosing class (if the class was declared as static), or an instance of the enclosing class.

However, in this case there is no enclosing class of Vec2, unless Scala adds one internally (though, java.lang.Class.getEnclosingClass() returns null for me).

So my question is how to instantiate Scala classes using reflection? Are there any additional parameters Scala constructors expect implicitly?

Subscript answered 2/2, 2011 at 7:24 Comment(0)
B
11

The newInstance method takes a varargs parameter. In Scala (unlike Java), you can't just pass an array and have it automatically treated as all the arguments. If that's what you want you need to do it explicitly like this:

ctor.newInstance(params:_*)

What you're doing at the moment is passing an array with 2 elements as the first argument to the constructor.

Bicentenary answered 2/2, 2011 at 8:17 Comment(4)
newInstance excepts arguments of java.lang.Object subtype, so you will rather have to do something like ctor.newInstance(params.map(_.asInstanceOf[Object]): _*) for FloatRoxane
Thanks, that did help! Also thanks Vasil. I did not run into that problem with the generic class I'm working on, which creates an Array[AnyRef]. The example I posted was a simplified version. :)Subscript
@Vasil As with Java, Scala will autobox anything that has an underlying primitive type, so a Scala Float can be either a float or a java.lang.Float. Because scala varargs use Seq and Seq is an erased type, you can be certain that what you're passing will already be boxed and will subclass ObjectCowskin
@Kevin Yes, I agree on Seq. I was keeping in mind Array type that used in the exampleRoxane

© 2022 - 2024 — McMap. All rights reserved.