我如何在Scala中创建一个没有参数的构造函数参数的Case Class实例?
我如何在Scala中创建一个没有参数的构造函数参数的Case Class实例?
我正在制作一个使用反射设置字段值的Scala应用程序。这个功能正常工作。
然而,为了设置字段值,我需要一个已创建的实例。如果我有一个带有空构造函数的类,我可以很容易地使用classOf [Person] .getConstructors....来做到这一点。
然而,当我尝试使用具有非空构造函数的Case类时,它不起作用。我有所有的字段名称及其值,以及我需要创建的对象类型。我能以某种方式实例化Case类吗?
唯一我没有的是Case类构造函数的参数名称,或者在没有参数的情况下创建它,然后通过反射设置值的方法。
让我们看个例子。
我有以下内容:
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) = ... } //这个可以工作 val dog = classOf[Dog].newInstance() new Reflector(dog).setValue("name", "Doggy") //这个不行 val person = classOf[Person].newInstance //不起作用 val ctor = classOf[Person].getConstructors()(0) ctor.newInstance(parameters) //我有属性名称和值,但我不知道它们哪个是每个参数的,也不知道构造函数参数的名称
问题的原因是希望在Scala中创建一个没有构造参数的Case Class实例。解决方法是使用一个名为`ClassUtil`的对象,在该对象中定义了一个用于创建实例的方法`newInstance`。该方法接受一个`Class[_ <: AnyRef]`类型的参数,返回一个`AnyRef`类型的实例。
下面是解决问题的代码:
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") } } }
上述代码中的`newInstance`方法使用了`findNoArgOrPrimaryCtor`和`getCtorDefaultArgs`两个私有方法。`findNoArgOrPrimaryCtor`方法根据类的构造函数参数数量选择使用无参构造函数还是主构造函数。`getCtorDefaultArgs`方法根据构造函数的参数类型和索引获取默认参数的方法名,并通过反射调用这些方法获取默认参数的值。
通过使用`ClassUtil.newInstance`方法,可以创建一个没有构造参数的Case Class实例。
问题的原因是,尽管为某个类添加了默认参数,但是通过反射获取构造函数时,仍然会得到带有所有参数的构造函数。而且由于只有类型信息,无法手动调用apply方法,只能通过反射来调用。而且,如果通过反射调用,仍然会得到所有参数,不会检查默认值。
解决方法是使用反射来确定哪些参数有默认值,然后为其他参数提供null或零值。
具体的解决方法是创建一个名为newCase的方法,该方法通过反射获取类的伴生对象,并使用伴生对象的apply方法创建一个实例。然后创建一个名为default的方法,该方法根据参数的类型返回默认值。最后,在newCase方法中调用default方法,为参数列表提供默认值,并调用apply方法创建实例。
在代码中,首先导入了reflect和scala.reflect.runtime.universe库。然后定义了一个名为Person的case class,该类具有一个默认参数age,且要求age大于等于18。接下来定义了一个名为Test的object,其中包含了一个名为newCase的方法和一个名为default的方法。最后,使用assert语句测试了创建实例的结果。
通过使用反射来获取类的伴生对象并调用apply方法,可以在没有参数的情况下创建一个带有构造函数参数的case class实例。如果存在默认参数,可以使用反射来确定哪些参数有默认值,并为其他参数提供null或零值。
在Scala中,如果你想要实例化一个没有参数的对象,你可以像你在例子中所做的那样,只要你的反射setter能够处理设置不可变的vals。你可以提供一个替代的构造器,如下所示:
case class Person(name : String, age : Int) { def this() = this("", 0) }
注意,case类不会生成一个零参数的伴生对象,所以你需要像这样实例化它:`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)
谢谢!然而,我知道这个方法,但我不希望使用这个方法的用户必须创建这个具有默认值的默认构造函数,这些默认值永远不会有效。你知道其他的方法吗?
如果变量在类中,它们要么为null,要么有一些默认值。你可以将name设置为null,但是int类型需要一些类型的默认值,因为它不能为null。你可以使用反射来尝试适当地调用构造函数,但我认为在提前了解传入的内容方面,你需要知道很多事情。
例如,在这种情况下,name和age的默认值是什么?没有。我实际上会在代码中添加一个`require(name != null)`。如果我这样做,并且我提供null作为构造函数参数,我会得到一个异常。
那么,你会说为了使用这个,你会让这个“框架”的用户为他们想要用这个创建的所有类型创建一个默认构造函数?
这完全取决于情况,我也不太确定你的使用场景是什么。在你的例子中,你将`name`的默认值设置为`Tony`,这就是为什么我提供了空字符串。我认为要求实现零参数构造器对于一个框架来说并不是不寻常的要求。