7

反射フィールドの値で設定するScalaアプリを作成しています。これは問題なく動作します。

ただし、フィールド値を設定するには、インスタンスを作成する必要があります。空のコンストラクターを持つクラスがある場合、classOf [Person].getConstructors...を使用してこれを簡単に行うことができます。

ただし、空でないコンストラクターを使用してCaseクラスでこれを実行しようとすると、機能しません。すべてのフィールド名とその値、および作成する必要のあるオブジェクトタイプがあります。自分が持っているものを使って、Case Classをなんとかしてインスタンス化できますか?

私が持っていないのは、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

4 に答える 4

4

引数なしでオブジェクトをインスタンス化する方法を探している場合は、リフレクションセッターが不変の値の設定を処理できる限り、例で行ったのと同じことを行うことができます。

以下のように、代替コンストラクターを提供します。

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)
于 2012-12-11T02:29:38.890 に答える
4

ケースクラスにはデフォルトの引数が必要ですPerson()。デフォルトの引数がない場合、name に null を指定すると、require(name != null) にヒットする可能性があります (またはヒットするはずです)。

または、リフレクションを使用して、デフォルトを持つパラメーターを特定し、残りのパラメーターに 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]())
}
于 2012-12-11T03:03:34.453 に答える
2

以下のアプローチは、引数なしの ctor またはすべてデフォルトのプライマリ ctor を持つ任意の Scala クラスで機能します。

必要なのは Class[_] インスタンスであり、implicit などではないため、呼び出し時に利用可能な情報量について、他のいくつかの仮定よりも少ない仮定を行います。また、このアプローチは、クラスがケースクラスまたはまったく仲間を持っています。

参考までに、構築中は引数なしの 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")
    }
  }
}
于 2014-07-07T00:24:28.427 に答える
0

同様の問題に遭遇しました。Macro Paradise の使いやすさを考えると、Macro Annotations は解決策です (これまでのところ scala 2.10.X および 2.11 の場合)。

この質問と、以下のコメントにリンクされているサンプル プロジェクトを確認してください。

于 2014-04-01T03:30:44.553 に答える