【问题标题】:Instantiating a case class with default args via reflection通过反射实例化具有默认参数的案例类
【发布时间】:2019-03-10 23:36:56
【问题描述】:

我需要能够通过反射来实例化各种案例类,既要弄清楚构造函数的参数类型,又要使用所有默认参数调用构造函数。

我已经做到了:

import reflect.runtime.{universe => ru}
val m = ru.runtimeMirror(getClass.getClassLoader)

case class Bar(i: Int = 33)

val tpe      = ru.typeOf[Bar]
val classBar = tpe.typeSymbol.asClass
val cm       = m.reflectClass(classBar)
val ctor     = tpe.declaration(ru.nme.CONSTRUCTOR).asMethod
val ctorm    = cm.reflectConstructor(ctor)

// figuring out arg types
val arg1 = ctor.paramss.head.head
arg1.typeSignature =:= ru.typeOf[Int] // true
// etc.

// instantiating with given args
val p = ctorm(33)

现在缺少的部分:

val p2 = ctorm()  // IllegalArgumentException: wrong number of arguments

那么如何使用Bar 的默认参数创建p2,即没有反射的Bar()

【问题讨论】:

标签: scala reflection


【解决方案1】:

所以在链接的问题中,:power REPL 使用内部 API,这意味着 defaultGetterName 不可用,因此我们需要手动构建它。来自@som-snytt 的回答:

def newDefault[A](implicit t: reflect.ClassTag[A]): A = {
  import reflect.runtime.{universe => ru, currentMirror => cm}

  val clazz  = cm.classSymbol(t.runtimeClass)
  val mod    = clazz.companionSymbol.asModule
  val im     = cm.reflect(cm.reflectModule(mod).instance)
  val ts     = im.symbol.typeSignature
  val mApply = ts.member(ru.newTermName("apply")).asMethod
  val syms   = mApply.paramss.flatten
  val args   = syms.zipWithIndex.map { case (p, i) =>
    val mDef = ts.member(ru.newTermName(s"apply$$default$$${i+1}")).asMethod
    im.reflectMethod(mDef)()
  }
  im.reflectMethod(mApply)(args: _*).asInstanceOf[A]
}

case class Foo(bar: Int = 33)

val f = newDefault[Foo]  // ok

这真的是最短路径吗?

【讨论】:

  • 好的,没问题。无论如何,Scala 2.10 反射的出色工作,感谢 Eugene。
  • 4年后,还是这样吗?有什么改善吗?
【解决方案2】:

没有最小化……也没有背书……

scala> import scala.reflect.runtime.universe
import scala.reflect.runtime.universe

scala> import scala.reflect.internal.{ Definitions, SymbolTable, StdNames }
import scala.reflect.internal.{Definitions, SymbolTable, StdNames}

scala> val ds = universe.asInstanceOf[Definitions with SymbolTable with StdNames]
ds: scala.reflect.internal.Definitions with scala.reflect.internal.SymbolTable with scala.reflect.internal.StdNames = scala.reflect.runtime.JavaUniverse@52a16a10

scala> val n = ds.newTermName("foo")
n: ds.TermName = foo

scala> ds.nme.defaultGetterName(n,1)
res1: ds.TermName = foo$default$1

【讨论】:

  • 在 2.10 中需要为 ds.newTermName("foo")
【解决方案3】:

这是一个可以复制到代码库中的工作版本:

import scala.reflect.api
import scala.reflect.api.{TypeCreator, Universe}
import scala.reflect.runtime.universe._

object Maker {
  val mirror = runtimeMirror(getClass.getClassLoader)

  var makerRunNumber = 1

  def apply[T: TypeTag]: T = {
    val method = typeOf[T].companion.decl(TermName("apply")).asMethod
    val params = method.paramLists.head
    val args = params.map { param =>
      makerRunNumber += 1
      param.info match {
        case t if t <:< typeOf[Enumeration#Value] => chooseEnumValue(convert(t).asInstanceOf[TypeTag[_ <: Enumeration]])
        case t if t =:= typeOf[Int] => makerRunNumber
        case t if t =:= typeOf[Long] => makerRunNumber
        case t if t =:= typeOf[Date] => new Date(Time.now.inMillis)
        case t if t <:< typeOf[Option[_]] => None
        case t if t =:= typeOf[String] && param.name.decodedName.toString.toLowerCase.contains("email") => s"random-$arbitrary@give.asia"
        case t if t =:= typeOf[String] => s"arbitrary-$makerRunNumber"
        case t if t =:= typeOf[Boolean] => false
        case t if t <:< typeOf[Seq[_]] => List.empty
        case t if t <:< typeOf[Map[_, _]] => Map.empty
        // Add more special cases here.
        case t if isCaseClass(t) => apply(convert(t))
        case t => throw new Exception(s"Maker doesn't support generating $t")
      }
    }

    val obj = mirror.reflectModule(typeOf[T].typeSymbol.companion.asModule).instance
    mirror.reflect(obj).reflectMethod(method)(args:_*).asInstanceOf[T]
  }

  def chooseEnumValue[E <: Enumeration: TypeTag]: E#Value = {
    val parentType = typeOf[E].asInstanceOf[TypeRef].pre
    val valuesMethod = parentType.baseType(typeOf[Enumeration].typeSymbol).decl(TermName("values")).asMethod
    val obj = mirror.reflectModule(parentType.termSymbol.asModule).instance

    mirror.reflect(obj).reflectMethod(valuesMethod)().asInstanceOf[E#ValueSet].head
  }

  def convert(tpe: Type): TypeTag[_] = {
    TypeTag.apply(
      runtimeMirror(getClass.getClassLoader),
      new TypeCreator {
        override def apply[U <: Universe with Singleton](m: api.Mirror[U]) = {
          tpe.asInstanceOf[U # Type]
        }
      }
    )
  }

  def isCaseClass(t: Type) = {
    t.companion.decls.exists(_.name.decodedName.toString == "apply") &&
      t.decls.exists(_.name.decodedName.toString == "copy")
  }
}

而且,当你想使用它时,你可以调用:

val user = Maker[User]
val user2 = Maker[User].copy(email = "someemail@email.com")

上面的代码生成任意且唯一的值。数据并不是完全随机的。最适合在测试中使用。

它适用于 Enum 和嵌套案例类。您还可以轻松扩展它以支持其他一些特殊类型。

在此处阅读我们的完整博客文章:https://give.engineering/2018/08/24/instantiate-case-class-with-arbitrary-value.html

【讨论】:

    【解决方案4】:

    这是使用默认构造函数参数通过反射创建案例类的最完整示例(Github source):

    import scala.reflect.runtime.universe
    import scala.reflect.internal.{Definitions, SymbolTable, StdNames}
    
    object Main {
      def newInstanceWithDefaultParameters(className: String): Any = {
        val runtimeMirror: universe.Mirror = universe.runtimeMirror(getClass.getClassLoader)
        val ds = universe.asInstanceOf[Definitions with SymbolTable with StdNames]
        val classSymbol = runtimeMirror.staticClass(className)
        val classMirror = runtimeMirror.reflectClass(classSymbol)
        val moduleSymbol = runtimeMirror.staticModule(className)
        val moduleMirror = runtimeMirror.reflectModule(moduleSymbol)
        val moduleInstanceMirror = runtimeMirror.reflect(moduleMirror.instance)
        val defaultValueMethodSymbols = moduleMirror.symbol.info.members
          .filter(_.name.toString.startsWith(ds.nme.defaultGetterName(ds.newTermName("apply"), 1).toString.dropRight(1)))
          .toSeq
          .reverse
          .map(_.asMethod)
        val defaultValueMethods = defaultValueMethodSymbols.map(moduleInstanceMirror.reflectMethod).toList
        val primaryConstructorMirror = classMirror.reflectConstructor(classSymbol.primaryConstructor.asMethod)
        primaryConstructorMirror.apply(defaultValueMethods.map(_.apply()): _*)
      }
    
      def main(args: Array[String]): Unit = {
        val instance = newInstanceWithDefaultParameters(classOf[Bar].getName)
        println(instance)
      }
    }
    
    case class Bar(i: Int = 33)
    

    【讨论】:

      猜你喜欢
      • 2020-07-10
      • 1970-01-01
      • 2021-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-27
      • 1970-01-01
      • 2020-12-09
      相关资源
      最近更新 更多