Как я могу создать экземпляр класса Case с аргументами конструктора без параметров в Scala?
Я делаю приложение Scala, которое устанавливает значения поля отражения. Это работает хорошо.
Однако для установки значений полей мне нужен созданный экземпляр. Если у меня есть класс с пустым конструктором, я могу легко сделать это с помощью classOf[Person].getConstructors....
Однако, когда я пытаюсь сделать это с классом Case с непустым конструктором, это не работает. У меня есть все имена полей и их значения, а также тип объекта, который мне нужно создать. Могу ли я как-нибудь создать экземпляр класса Case с тем, что у меня есть?
Единственное, чего у меня нет - это имена параметров из конструктора 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
4 ответа
Если вы ищете способ создания экземпляра объекта без аргументов, вы можете сделать то же самое, что и в своем примере, лишь бы ваш установщик отражений мог обрабатывать установку неизменяемых значений.
Вы бы предоставили альтернативный конструктор, как показано ниже:
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)
Класс case должен иметь аргументы по умолчанию, так что вы можете просто Person()
; при отсутствии аргумента по умолчанию указание нулевого имени может (или должно) привести к требованию (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]())
}
Подход, приведенный ниже, работает для любого класса Scala, у которого есть c-аргумент без аргументов или первичный 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")
}
}
}
Я столкнулся с подобной проблемой. Учитывая простоту использования Macro Paradise, аннотации макросов являются решением (пока для scala 2.10.X и 2.11).
Проверьте этот вопрос и пример проекта, связанный в комментариях ниже.