Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create an instance of type T at runtime with TypeTags

Here below is how to create a new instance of type T at runtime with Manifest:

trait MyTrait
class MyClass1(val name: String) extends MyTrait
class MyClass2(val name: String) extends MyTrait

class Test[T <: MyTrait] {

  def createInstance[T](name: String)(implicit m: Manifest[T]): T = {
    m.runtimeClass.getConstructors()(0)
      .newInstance(name).asInstanceOf[T]
  }

  def doSomething() {
    val myClass = createInstance("joe")
    ...
  }
}

...

val test = new Test[MyClass1]
test.doSomething

The createInstance method above creates a new instance of one of the classes that implement MyTrait and invokes the constructor with the given string. How do I implement the same with TypeTag?


Reimplemented using scala.reflect.runtime._

Here below is class Test reimplemented as suggested by som-snytt:

class Test[T <: MyTrait] {

  import scala.reflect.runtime._
  import scala.reflect.runtime.universe._

  def createInstance[T: TypeTag](name: String): T = {
    val tt = typeTag[T]

    currentMirror.reflectClass(tt.tpe.typeSymbol.asClass).reflectConstructor(
      tt.tpe.members.filter(m =>
        m.isMethod && m.asMethod.isConstructor
      ).iterator.next.asMethod
    )(name).asInstanceOf[T]
  }   
}

As long as I call createInstance on Test it works:

val test = new Test[MyClass1]
val myClass = test.createInstance("hello") // this works

But as soon as I define a new class derived from Test...

class DerivedTest[T <: MyTrait] extends Test[T]

... and I invoke createInstance on that new class like this...

val test = new DerivedText[MyClass1]
val myClass = test.createInstance("hello") // this crashes

... I get the following error:

java.util.NoSuchElementException: next on empty iterator
    at scala.collection.Iterator$$anon$2.next(Iterator.scala:39)
    at scala.collection.Iterator$$anon$2.next(Iterator.scala:37)
    at scala.collection.LinearSeqLike$$anon$1.next(LinearSeqLike.scala:62)
    at DerivedTest$class.createInstance(<console>:27)
    at $anon$1.createInstance(<console>:26)
    at .<init>(<console>:28)
at .<clinit>(<console>)
    at .<init>(<console>:7)
    at .<clinit>(<console>)
    at $print(<console>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
...

Am I missing something?

like image 771
j3d Avatar asked Feb 11 '14 23:02

j3d


1 Answers

Eventually here is the implementation that actually works - please just assume this answer comes from som-snytt:

import scala.reflect.runtime._
import scala.reflect.runtime.universe._

class Test[T <: MyTrait : TypeTag] {

  def createInstance(args: AnyRef*)(ctor: Int = 0): T = {
    val tt = typeTag[T]

    currentMirror.reflectClass(tt.tpe.typeSymbol.asClass).reflectConstructor(
      tt.tpe.members.filter(m =>
        m.isMethod && m.asMethod.isConstructor
      ).iterator.toSeq(ctor).asMethod
    )(args: _*).asInstanceOf[T]
  }   
}

I hope that helps.

like image 129
j3d Avatar answered Nov 15 '22 19:11

j3d