Dyin
Dyin

Reputation: 5376

Instantiate type-parametrized class at runtime with default constructor

I want to instantiate a new object with the following classes:

class Test[T : ClassTag](val value : T) extends Serializable

private object Test {
  private var testClass : Class[_] = _

  def setTestClass(test : Class[_]) : Unit = {
    testClass = test
  }

  def apply[T : ClassTag](value : T) : Test[T] = {
    testClass.getConstructors()(0).newInstance(value).asInstanceOf[Test[T]]
  }
}

class Subtest[T : ClassTag](
    override val value : T, 
    val additional : Integer
) extends Test[T](value)

I set the test-class with setTestClass somewhere, that has been loaded with Class.forName from a string. Let say it is "Subtest". I wish to instantiate a new Subtest[String] with - for example the following code:

Test("my random string") // It should return a new Subtest[String]

The problem is that newInstance throws java.lang.IllegalArgumentException: wrong number of arguments. I've checked the parameterTypes of the Constructor, and there are two parameter types:

Okay. How to supply the ClassTag? What is the problem here? Is there a better approach, workaround for this?

Upvotes: 1

Views: 1557

Answers (1)

Ben Reich
Ben Reich

Reputation: 16324

The T: ClassTag syntax is called a context bound. It is syntactic sugar for an implicit parameter of type ClassTag[T]. In other words, the following class signatures are equivalent:

class Test[T : ClassTag](val value : T)
class Test[T](val value: T)(implicit val ev: ClassTag[T])

So the constructor you're looking for expects another parameter. When using the context-bound syntax, you can always use the implicitly to get the desired parameter (which is guaranteed to exist because of the semantics of the context bound). Alternatively, you can use the classTag operator to directly get an instance of ClassTag[T] when you know it is available.

Another detail is that when using a type parameter in an object like this, it needs to be an AnyRef.

So your apply method might look like:

def apply[T <: AnyRef : ClassTag](value : T) : Test[T] = {
    testClass.getConstructors.head.newInstance(value, classTag[T]).asInstanceOf[Test[T]]
}

Upvotes: 3

Related Questions