How to create an instance of type T at runtime wit

2020-02-17 07:58发布

问题:

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?

回答1:

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.



回答2:

Maybe someone who does this all the time can chime in, but it took me this many steps to reproduce it.

I start the REPL -i, so autocomplete is broken. That puts me at a disadvantage.

scala> class X(i: Int)
defined class X

scala> typeTag[X]
res0: reflect.runtime.universe.TypeTag[X] = TypeTag[X]

scala> .tpe
res1: reflect.runtime.universe.Type = X

scala> .members
res2: reflect.runtime.universe.MemberScope = Scopes(constructor X, value i, method $asInstanceOf, method $isInstanceOf, method synchronized, method ##, method !=, method ==, method ne, method eq, constructor Object, method notifyAll, method notify, method clone, method getClass, method hashCode, method toString, method equals, method wait, method wait, method wait, method finalize, method asInstanceOf, method isInstanceOf, method !=, method ==)

scala> res2.filter(s => s.isMethod && s.asMethod.isConstructor)
res4: Iterable[reflect.runtime.universe.Symbol] = SynchronizedOps(constructor X, constructor Object)

scala> res4.iterator.next
res7: reflect.runtime.universe.Symbol = constructor X

scala> .typeSignature
res8: reflect.runtime.universe.Type = (i: scala.Int)X

scala> res7.asMethod
res11: reflect.runtime.universe.MethodSymbol = constructor X

scala> res1.typeSymbol.asClass
res13: reflect.runtime.universe.ClassSymbol = class X

scala> currentMirror reflectClass res13
res14: reflect.runtime.universe.ClassMirror = class mirror for X (bound to null)

scala> res14 reflectConstructor res11
res16: reflect.runtime.universe.MethodMirror = constructor mirror for X.<init>(i: scala.Int): X (bound to null)

scala> res16(7)
res17: Any = X@28730c5a

scala> .asInstanceOf[X]
res18: X = X@28730c5a


回答3:

j3d,

I believe the manifest approach is deprecated. You can use scala code below to create an instance via reflection. You will have to adjust the code slightly to factor for classes with multiple constructors, and/or arguments

Code is based on http://docs.scala-lang.org/overviews/reflection/overview.html

import scala.reflect.runtime.{universe => ru}
import ru._

...

class Person { }


def example() = 
{
   val instance1 = createInstance[Person]()
   val instance2 = createInstance(typeOf[Person])
}


def createInstance[T:TypeTag]() : Any= {
    createInstance(typeOf[T])
}


def createInstance(tpe:Type): Any = {
    val mirror = ru.runtimeMirror(getClass.getClassLoader)
    val clsSym = tpe.typeSymbol.asClass
    val clsMirror = mirror.reflectClass(clsSym)
    val ctorSym = tpe.decl(ru.termNames.CONSTRUCTOR).asMethod
    val ctorMirror = clsMirror.reflectConstructor(ctorSym)
    val instance = ctorMirror()
    return instance
}


回答4:

object BeanFactory {

  import scala.reflect.runtime.universe._
  import scala.reflect.runtime.{universe => ru}

  def createBean[T: TypeTag](): Option[T] = {
    val typee = ru.typeOf[T]
    val constructor = typee.decl(ru.termNames.CONSTRUCTOR).asMethod
    if (constructor.isPrivate) {
      println("private class can not created ")
      None
    } else {
      val classMirror = ru.runtimeMirror(getClass.getClassLoader).reflectClass(typee.typeSymbol.asClass)
      val constructorMethod = classMirror.reflectConstructor(constructor)
      val params = constructor.paramLists.flatten.map(par => {
        if (par.typeSignature =:= typeOf[Int]) {
          0
        } else {
          if (par.typeSignature =:= typeOf[String]) {
            ""
          } else {
            if (par.typeSignature =:= typeOf[Double]) {
              0.0
            } else {
              if (par.typeSignature =:= typeOf[Float]) {
                0.0f
              } else {
                if (par.typeSignature =:= typeOf[Char]) {
                  ""
                } else {
                  if (par.typeSignature =:= typeOf[Boolean]) {
                    false
                  } else {
                    null
                  }
                }
              }
            }
          }
        }

      })
      Some(constructorMethod(params: _*).asInstanceOf[T])
    }
  }
}

This may end up with your problem