如何在 Scala 中使用没有参数的构造函数参数创建 Case Class 的实例?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13812172/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I create an instance of a Case Class with constructor arguments with no Parameters in Scala?
提问by mgonto
I'm making a Scala app that sets by reflection field values. This works OK.
我正在制作一个通过反射字段值设置的 Scala 应用程序。这工作正常。
However, in order to set field values I need a created instance. If I have a class with an empty constructor, I can do this easily with classOf[Person].getConstructors....
但是,为了设置字段值,我需要一个创建的实例。如果我有一个带有空构造函数的类,我可以使用 classOf[Person].getConstructors 轻松做到这一点....
However, when I try doing this with a Case class with a non empty constructor It doesn't work. I have all of the field names and its values, as well as the Object type I need to create. Can I instance the Case Class somehow with what I've got?
但是,当我尝试使用带有非空构造函数的 Case 类执行此操作时,它不起作用。我有所有的字段名称及其值,以及我需要创建的对象类型。我可以用我所拥有的以某种方式实例化 Case Class 吗?
The only thing I don't have is the parameter names from the Case Class constructor or a way to create this without parameter and then setting the values via reflection.
我唯一没有的是来自 Case Class 构造函数的参数名称,或者一种在没有参数的情况下创建它然后通过反射设置值的方法。
Let's go to the example.
我们来看例子。
I have the following
我有以下
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
回答by som-snytt
The case class should have default args, so that you can just Person(); in the absence of a default arg, supplying a null for name might (or ought to) hit a require(name != null).
case 类应该有默认参数,这样你就可以Person(); 在没有默认参数的情况下,为 name 提供 null 可能(或应该)达到 require(name != null)。
Alternatively, use reflection to figure out which params have defaults and then supply nulls or zeros for the rest.
或者,使用反射来确定哪些参数具有默认值,然后为其余参数提供空值或零值。
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]())
}
回答by jcern
If you are looking for a way to instantiate the object with no arguments, you could do the same as you did in your example, just so long as your reflection setter can handle setting the immutable vals.
如果您正在寻找一种不带参数实例化对象的方法,您可以像在您的示例中所做的一样,只要您的反射设置器可以处理设置不可变值。
You would provide an alternate constructor, as below:
您将提供一个备用构造函数,如下所示:
case class Person(name : String, age : Int) {
def this() = this("", 0)
}
Note that the case class will not generate a zero-arg companion object, so you will need to instantiate it as: new Person()or classOf[Person].newInstance(). However, that should be what you are looking to do.
请注意,case 类不会生成零参数的伴随对象,因此您需要将其实例化为:new Person()或classOf[Person].newInstance()。但是,这应该是您想要做的。
Should give you output like:
应该给你这样的输出:
scala> case class Person(name : String, age : Int) {
| def this() = this("", 0)
| }
defined class Person
scala> classOf[Person].newInstance()
res3: Person = Person(,0)
回答by John Lonergan
The approach below works for any Scala class that has either a no-arg ctor or has an all defaulted primary ctor.
下面的方法适用于任何具有无参数构造函数或具有所有默认主构造函数的 Scala 类。
It makes fewer assumptions than some others about how much information is available at the point of call as all it needs is a Class[_] instance and not implicits etc. Also the approach does not rely on the class having to be a case class or having a companion at all.
它比其他一些假设更少关于调用点可用信息的假设,因为它只需要一个 Class[_] 实例而不是隐式等。此外,该方法不依赖于必须是 case 类或有一个同伴。
FYI During construction precedence is given to the no-arg ctor if present.
仅供参考 如果存在,则在构建期间优先考虑无参数 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")
}
}
}
回答by Julian Peeters
I ran into a similar problem. Given the ease of using Macro Paradise, Macro Annotations are a solution (for scala 2.10.X and 2.11 so far).
我遇到了类似的问题。鉴于宏天堂的易用性,宏注释是一种解决方案(目前适用于 Scala 2.10.X 和 2.11)。
Check out this questionand the example project linked in the comments below.
查看这个问题和下面评论中链接的示例项目。

