【问题标题】:How can I create an instance of a Case Class with constructor arguments with no Parameters in Scala?如何在 Scala 中使用没有参数的构造函数参数创建案例类的实例?
【发布时间】:2014-07-25 09:04:21
【问题描述】:

我正在制作一个通过反射字段值设置的 Scala 应用程序。这工作正常。

但是,为了设置字段值,我需要一个已创建的实例。如果我有一个带有空构造函数的类,我可以使用 classOf[Person].getConstructors.... 轻松做到这一点。

但是,当我尝试使用具有非空构造函数的 Case 类执行此操作时,它不起作用。我拥有所有字段名称及其值,以及我需要创建的 Object 类型。我可以用我所拥有的东西以某种方式实例化案例类吗?

我唯一没有的是 Case Class 构造函数中的参数名称,或者是在没有参数的情况下创建它然后通过反射设置值的方法。

我们来看例子。

我有以下

case class Person(name : String, age : Int)
class Dog(name : String) {
    def this() = {
        name = "Tony"
    }
}

class Reflector[O](obj : O) {

    def setValue[F](propName : String, value : F) = ...

    def getValue(propName : String) = ...
}

//This works
val dog = classOf[Dog].newInstance()
new Reflector(dog).setValue("name", "Doggy")

//This doesn't
val person = classOf[Person].newInstance //Doesn't work

val ctor = classOf[Person].getConstructors()(0)
val ctor.newInstance(parameters) //I have the property names and values, but I don't know 
// which of them is for each parameter, nor I name the name of the constructor parameters

【问题讨论】:

    标签: scala reflection macros case-class scala-macros


    【解决方案1】:

    如果您正在寻找一种不带参数来实例化对象的方法,您可以像在示例中那样做,只要您的反射设置器可以处理设置不可变 val。

    您将提供一个替代构造函数,如下所示:

    case class Person(name : String, age : Int) {
        def this() = this("", 0)
    }
    

    请注意,案例类不会生成零参数伴随对象,因此您需要将其实例化为:new Person()classOf[Person].newInstance()。但是,这应该是您想要做的。

    应该给你这样的输出:

    scala> case class Person(name : String, age : Int) {
         |         def this() = this("", 0)
         |     }
    defined class Person
    
    scala> classOf[Person].newInstance()
    res3: Person = Person(,0)
    

    【讨论】:

    • 谢谢!。但是,我知道这一点,但我不希望它的用户必须使用永远无效的默认值创建这个默认构造函数。你知道另一种方法吗?
    • @mgonto,如果变量在类中,它们将为空或具有一些默认值。您始终可以将 name 设置为 null,但 int 需要某种类型的默认值,因为它不能为 null。您可以使用反射来尝试适当地调用构造函数,但我认为您需要提前了解很多有关正在传递的内容。
    • 例如,在这种情况下,name 和 age 的默认值是哪个?不会有。我实际上会在代码中添加一个 require(name != null) 。如果我这样做并提供 null 作为构造函数参数,则会出现异常。
    • 所以,你会说为了使用它,你会让这个“框架”的用户为他们想要创建它的所有类型创建一个默认构造函数?
    • 这一切都取决于,我不完全确定你的使用场景是什么。在您的示例中,您将name 的默认值设置为Tony,这就是我提供空字符串的原因。我不认为框架要求还实现零参数构造函数是不典型的。
    【解决方案2】:

    案例类应该有默认参数,这样你就可以Person();在没有默认参数的情况下,为 name 提供 null 可能(或应该)命中 require(name != null)。

    或者,使用反射来确定哪些参数具有默认值,然后为其余参数提供空值或零。

    import reflect._
    import scala.reflect.runtime.{ currentMirror => cm }
    import scala.reflect.runtime.universe._
    
    // case class instance with default args
    
    // Persons entering this site must be 18 or older, so assume that
    case class Person(name: String, age: Int = 18) {
      require(age >= 18)
    }
    
    object Test extends App {
    
      // Person may have some default args, or not.
      // normally, must Person(name = "Guy")
      // we will Person(null, 18)
      def newCase[A]()(implicit t: ClassTag[A]): A = {
        val claas = cm classSymbol t.runtimeClass
        val modul = claas.companionSymbol.asModule
        val im = cm reflect (cm reflectModule modul).instance
        defaut[A](im, "apply")
      }
    
      def defaut[A](im: InstanceMirror, name: String): A = {
        val at = newTermName(name)
        val ts = im.symbol.typeSignature
        val method = (ts member at).asMethod
    
        // either defarg or default val for type of p
        def valueFor(p: Symbol, i: Int): Any = {
          val defarg = ts member newTermName(s"$name$$default$$${i+1}")
          if (defarg != NoSymbol) {
            println(s"default $defarg")
            (im reflectMethod defarg.asMethod)()
          } else {
            println(s"def val for $p")
            p.typeSignature match {
              case t if t =:= typeOf[String] => null
              case t if t =:= typeOf[Int]    => 0
              case x                        => throw new IllegalArgumentException(x.toString)
            }
          }
        }
        val args = (for (ps <- method.paramss; p <- ps) yield p).zipWithIndex map (p => valueFor(p._1,p._2))
        (im reflectMethod method)(args: _*).asInstanceOf[A]
      }
    
      assert(Person(name = null) == newCase[Person]())
    }
    

    【讨论】:

    • 我已经尝试过默认的案例类。即使我为某个类添加了默认值,如果我通过反射获取构造函数,我也会得到带有所有参数的构造函数。而且我只有类型,我不能手动调用 apply 方法,而是通过反射。如果我通过反射调用它,我仍然会得到所有参数,它不会检查默认值
    • 如果我为构造函数的参数提供 null ,那么假设该字段不为 null 或者如果有你所说的 require ,它可能会遇到异常,所以我不认为这是完整的解决方案。你有别的想法吗?谢谢!
    • 参数的默认值在伴随对象上。例如,我正要编写代码;我每次都忘记具体是怎么做的,所以这是一个有用的练习。我的想法是,如果可用,使用这些默认值(命名为 default$foo$1 或其他),否则尝试 null 或零。
    • 顺便说一句,另一种颠覆 ctor 的方法是序列化。我认为我没有看到用于 DI 的情况。我想如果你有一个原型对象,它是在内存中还是序列化并不重要。
    【解决方案3】:

    以下方法适用于任何具有无参数 ctor 或具有全默认主 ctor 的 Scala 类。

    它比其他一些假设更少关于在调用点有多少信息可用,因为它只需要一个 Class[_] 实例而不是隐含等。此外,该方法不依赖于必须是一个类案例课或有同伴。

    仅供参考,在构造过程中,如果存在无参数 ctor,则优先级。

    object ClassUtil {
    
    def newInstance(cz: Class[_ <: AnyRef]): AnyRef = {
    
        val bestCtor = findNoArgOrPrimaryCtor(cz)
        val defaultValues = getCtorDefaultArgs(cz, bestCtor)
    
        bestCtor.newInstance(defaultValues: _*).asInstanceOf[A]
      }
    
      private def defaultValueInitFieldName(i: Int): String = s"$$lessinit$$greater$$default$$${i + 1}"
    
      private def findNoArgOrPrimaryCtor(cz: Class[_]): Constructor[_] = {
        val ctors = cz.getConstructors.sortBy(_.getParameterTypes.size)
    
        if (ctors.head.getParameterTypes.size == 0) {
          // use no arg ctor
          ctors.head
        } else {
          // use primary ctor
          ctors.reverse.head
        }
      }
    
      private def getCtorDefaultArgs(cz: Class[_], ctor: Constructor[_]): Array[AnyRef] = {
    
        val defaultValueMethodNames = ctor.getParameterTypes.zipWithIndex.map {
          valIndex => defaultValueInitFieldName(valIndex._2)
        }
    
        try {
          defaultValueMethodNames.map(cz.getMethod(_).invoke(null))
        } catch {
          case ex: NoSuchMethodException =>
            throw new InstantiationException(s"$cz must have a no arg constructor or all args must be defaulted")
        }
      }
    }
    

    【讨论】:

      【解决方案4】:

      我遇到了类似的问题。鉴于使用 Macro Paradise 的便利性,Macro Annotations 是一种解决方案(目前适用于 scala 2.10.X 和 2.11)。

      查看this question 和下面 cmets 中链接的示例项目。

      【讨论】:

        猜你喜欢
        • 2011-11-09
        • 1970-01-01
        • 1970-01-01
        • 2010-11-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多