我正在尝试使用Scala反射(Scala 2.11)将Scala Map [String,Any]转换为case类,如下所示 -
val m = Map("name" -> "ABC", "age" -> 7, "gender" -> "male")
case class someCC(name: String, age: Int, gender: String)
import scala.reflect.ClassTag
def createCaseClass[T](someMap : Map[String, Any])(implicit someClassTag : ClassTag[T]) = {
val ctor = someClassTag.runtimeClass.getConstructors.head
val args = someClassTag.runtimeClass.getDeclaredFields.map(x => someMap(x.getName))
ctor.newInstance(args: _*).asInstanceOf[T]
}
这很不幸导致编译错误 -
Name: Unknown Error
Message: <console>:106: error: type mismatch;
found : Array[Any]
required: Array[_ <: Object]
Note: Any >: Object, but class Array is invariant in type T.
You may wish to investigate a wildcard type such as `_ >: Object`. (SLS 3.2.10)
ctor.newInstance(args: _*).asInstanceOf[T]
^
我是使用ClassTags的新手,我理解这个错误主要是因为java.lang.Object是Any的子集,Any可以包含非java对象。
当我尝试用AnyRef(对应于JRE中的java.lang.Object)替换Any时,函数调用会导致类型不匹配错误。
import scala.reflect.ClassTag
def createCaseClass[T](someMap : Map[String, AnyRef])(implicit someClassTag : ClassTag[T]) = {
val ctor = someClassTag.runtimeClass.getConstructors.head
val args = someClassTag.runtimeClass.getDeclaredFields.map(x => someMap(x.getName))
ctor.newInstance(args: _*).asInstanceOf[T]
}
val someCC = createCaseClass[someCC](m)
Name: Unknown Error
Message: <console>:106: error: type mismatch;
found : scala.collection.immutable.Map[String,Any]
required: Map[String,AnyRef]
val someCC = createCaseClass[someCC](m)
解决此错误的最佳方法是什么?建议表示赞赏。谢谢!
更新1 - 更新此项以隐式地将Any转换为AnyRef会导致函数调用中出现错误'java.util.NoSuchElementException'。
import scala.reflect.ClassTag
import scala.reflect.runtime.universe._
def createMyClass[T](someMap : Map[String, Any])(implicit someClassTag : ClassTag[T]) = {
val ctor = someClassTag.runtimeClass.getConstructors.head
val args = someClassTag.runtimeClass.getDeclaredFields.map(x => someMap(x.getName))
ctor.newInstance(args.asInstanceOf[Seq[AnyRef]]: _*).asInstanceOf[T]
}
val m = Map("name" -> "ABC", "age" -> 7, "gender" -> "male")
case class someCC(name: String, age: Int, gender: String)
createMyClass[someCC](m)
Name: java.util.NoSuchElementException
Message: key not found: $outer
StackTrace: at scala.collection.MapLike$class.default(MapLike.scala:228)
at scala.collection.AbstractMap.default(Map.scala:59)
at scala.collection.MapLike$class.apply(MapLike.scala:141)
at scala.collection.AbstractMap.apply(Map.scala:59)
at $$$e75186ae1b35495ffea8e318378149a$$$$anonfun$1.apply(<console>:135)
at $$$e75186ae1b35495ffea8e318378149a$$$$anonfun$1.apply(<console>:135)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:186)
at scala.collection.TraversableLike$class.map(TraversableLike.scala:234)
at scala.collection.mutable.ArrayOps$ofRef.map(ArrayOps.scala:186)
at createMyClass(<console>:135)
我在这里做错了什么?
答案 0 :(得分:2)
使用演员:
ctor.newInstance(args.asInstanceOf[Seq[AnyRef]]: _*).asInstanceOf[T]
请注意,您将案例类的每个字段映射到构造函数参数。这是不正确的,因为案例类可以包含不在构造函数中的字段,并且您的代码将会中断。
更好的想法是使用Scala反射:
import reflect.runtime.universe._
def mkClassInstance[T: TypeTag](args: Map[String, Any]): T = {
val rMirror = runtimeMirror(getClass.getClassLoader)
val cMirror = rMirror.reflectClass(typeOf[T].typeSymbol.asClass)
// The primary constructor is the first one
val ctor = typeOf[T].decl(termNames.CONSTRUCTOR).asTerm.alternatives.head.asMethod
val argList = ctor.paramLists.flatten.map(param => args(param.name.toString))
cMirror.reflectConstructor(ctor)(argList: _*).asInstanceOf[T]
}
def mkInnerClassInstance[T: TypeTag](outer: Any)(args: Map[String, Any]): T = {
val rMirror = runtimeMirror(getClass.getClassLoader)
val cMirror = rMirror.reflect(outer).reflectClass(typeOf[T].typeSymbol.asClass)
// The primary constructor is the first one
val ctor = typeOf[T].decl(termNames.CONSTRUCTOR).asTerm.alternatives.head.asMethod
val argList = ctor.paramLists.flatten.map(param => args(param.name.toString))
cMirror.reflectConstructor(ctor)(argList: _*).asInstanceOf[T]
}