如何在运行时使用TypeTags创建类型T的实例

11
下面是如何使用“Manifet”在运行时创建类型为T的新实例:
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

上面的createInstance方法创建了一个实现MyTrait接口的类的新实例,并使用给定的字符串调用构造函数。如何使用TypeTag实现相同的功能?

使用scala.reflect.runtime._重新实现

下面是按照som-snytt的建议重新实现的Test类:
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]
  }   
}

只要我在 Test 上调用 createInstance 就可以正常运作:
val test = new Test[MyClass1]
val myClass = test.createInstance("hello") // this works

但是一旦我定义了一个继承自 Test 的新类...
class DerivedTest[T <: MyTrait] extends Test[T]

我创建了一个新类,然后使用createInstance方法调用该类...

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

我遇到了以下错误:

...

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)
...

我有什么遗漏吗?


3
createInstance 方法中的 T 遮蔽了 Test 类中的 T。这可能不是您想要的。 - ghik
是的,@ghik 告诉过你不要遮蔽 T。在这里,你为 ctor 提供了一个类型参数,但没有为 createInstance 提供。 - som-snytt
也许我漏了什么......但是 createInstance 应该在 'Test' 类中创建类型为 T 的实例。 - j3d
4个回答

6
最终,这里是实际可行的实现 - 请假设这个答案来自 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]
  }   
}

我希望您能够从中受益。

1
你只需要在Test或createInstance中的一个上添加类型参数。如果在Test上,就不必有val tt,或者将其变为def。TypeTag成为隐式类参数,class Test[T]()(implicit x: TypeTag[T])。作为类型限定,就像你展示的那样,你可以使用implicitly[TypeTag[T]]来获取它。 - som-snytt
是的,你说得对,我昨晚有点累了。已经修复了。再次感谢你。 - j3d

4
也许经常这样做的人可以加入,但是我需要这么多步骤才能重现它。
我启动REPL -i,所以自动完成不起作用。这使我处于不利地位。
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

目前是冗长的,会在https://issues.scala-lang.org/browse/SI-8221中修复。 - Eugene Burmako
@EugeneBurmako,你总是比我们Stack Overflow上的猴子们领先一步,有时候甚至是两步。 - som-snytt
Tx... 只要我不从派生类中调用 createInstance 方法,这个方法就能正常工作。但是如果我从派生类中调用 createInstance,就会一直出现错误 java.util.NoSuchElementException: next on empty iterator,因为它找不到构造函数。 - j3d
如果我理解正确的话,@j3d,是的,你必须调用createInstance[MyClass1],这样它才知道你要反射什么。你不能假设它正在推断你想要的类型。 - som-snytt
我刚刚更新了我的帖子并添加了更多细节。有没有解决方法? - j3d

2

j3d,

我相信清单(manifest)方法已经过时了。 您可以使用以下Scala代码通过反射创建实例。 您需要稍微调整代码来处理具有多个构造函数和/或参数的类。

代码基于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
}

0
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])
    }
  }
}

这可能会解决你的问题


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接