嗯...您可以使用反射为您的案例类创建任意copiers 甚至updaters。
不同之处在于updater 会更新case class instance,而copier 会创建具有更新字段的新副本。
updater 的实现可以如下完成,
import scala.language.existentials
import scala.reflect.runtime.{universe => ru}
def copyInstance[C: scala.reflect.ClassTag](instance: C, mapOfUpdates: Map[String, T forSome {type T}]): C = {
val runtimeMirror = ru.runtimeMirror(instance.getClass.getClassLoader)
val instanceMirror = runtimeMirror.reflect(instance)
val tpe = instanceMirror.symbol.toType
val copyMethod = tpe.decl(ru.TermName("copy")).asMethod
val copyMethodInstance = instanceMirror.reflectMethod(copyMethod)
val updates = tpe.members
.filter(member => member.asTerm.isCaseAccessor && member.asTerm.isMethod)
.map(member => {
val term = member.asTerm
//check if we need to update it or use the instance value
val updatedValue = mapOfUpdates.getOrElse(
key = term.name.toString,
default = instanceMirror.reflectField(term).get
)
updatedValue
}).toSeq.reverse
val copyOfInstance = copyMethodInstance(updates: _*).asInstanceOf[C]
copyOfInstance
}
def updateInstance[C: scala.reflect.ClassTag](instance: C, mapOfUpdates: Map[String, T forSome {type T}]): C = {
val runtimeMirror = ru.runtimeMirror(instance.getClass.getClassLoader)
val instanceMirror = runtimeMirror.reflect(instance)
val tpe = instanceMirror.symbol.toType
tpe.members.foreach(member => {
val term = member.asTerm
term.isCaseAccessor && term.isMethod match {
case true =>
// it is a case class accessor, check if we need to update it
mapOfUpdates.get(term.name.toString).foreach(updatedValue => {
val fieldMirror = instanceMirror.reflectField(term.accessed.asTerm)
// filed mirrors can even update immutable fields !!
fieldMirror.set(updatedValue)
})
case false => // Not a case class accessor, do nothing
}
})
instance
}
既然你想使用Options 来复制,这里是你的定义一次并与所有案例类一起使用copyUsingOptions
def copyUsingOptions[C: scala.reflect.ClassTag](instance: C, listOfUpdateOptions: List[Option[T forSome {type T}]]): C = {
val runtimeMirror = ru.runtimeMirror(instance.getClass.getClassLoader)
val instanceMirror = runtimeMirror.reflect(instance)
val tpe = instanceMirror.symbol.toType
val copyMethod = tpe.decl(ru.TermName("copy")).asMethod
val copyMethodInstance = instanceMirror.reflectMethod(copyMethod)
val updates = tpe.members.toSeq
.filter(member => member.asTerm.isCaseAccessor && member.asTerm.isMethod)
.reverse
.zipWithIndex
.map({ case (member, index) =>
listOfUpdateOptions(index).getOrElse(instanceMirror.reflectField(member.asTerm).get)
})
val copyOfInstance = copyMethodInstance(updates: _*).asInstanceOf[C]
copyOfInstance
}
现在您可以使用这些 updateInstance 或 copyInstance 来更新或复制任何案例类的实例,
case class Demo(id: Int, name: String, alliance: Option[String], power: Double, lat: Double, long: Double)
// defined class Demo
val d1 = Demo(1, "player_1", None, 15.5, 78.404, 71.404)
// d1: Demo = Demo(1,player_1,None,15.5,78.404,71.404)
val d1WithAlliance = copyInstance(d1, Map("alliance" -> Some("Empires")))
// d1WithAlliance: Demo = Demo(1,player_1,Some(Empires),15.5,78.404,71.404)
val d2 = copyInstance(d1, Map("id" -> 2, "name" -> "player_2"))
d2: Demo = Demo(2,player_2,None,15.5,78.404,71.404)
val d3 = copyWithOptions(
d1, List(Some(3),
Some("player_3"), Some(Some("Vikings")), None, None, None)
)
// d3: Demo = Demo(3,player_3,Some(Vikings),15.5,78.404,71.404)
// Or you can update instance using updateInstance
val d4 = updateInstance(d1, Map("id" -> 4, "name" -> "player_4"))
// d4: Demo = Demo(4,player_4,None,15.5,78.404,71.404)
d1
// d1: Demo = Demo(4,player_4,None,15.5,78.404,71.404)