【问题标题】:Scala Reflection to update a case class valScala反射更新案例类val
【发布时间】:2014-02-05 21:10:54
【问题描述】:

我在这里使用 scala 和 slick,并且我有一个 baserepository 负责完成我的课程的基本工作。 对于设计决策,我们确实将 updatedTime 和 createdTime 列全部由应用程序处理,而不是由数据库中的触发器处理。这两个字段都是 joda DataTime 实例。 这些字段在表的两个特征中定义,称为 HasUpdatedAt 和 HasCreatedAt

trait HasCreatedAt {
    val createdAt: Option[DateTime]
}

case class User(name:String,createdAt:Option[DateTime] = None) extends HasCreatedAt

我想知道如何使用反射调用用户复制方法,在数据库插入方法期间更新 createdAt 值。

在@vptron 和@kevin-wright cmets 之后编辑

我有一个这样的仓库

trait BaseRepo[ID, R] {

    def insert(r: R)(implicit session: Session): ID
  }

我只想实现一次插入,并且我想更新 createdAt,这就是我不使用复制方法的原因,否则我需要在使用 createdAt 列的任何地方实现它。

【问题讨论】:

  • 为什么需要反思? copy 方法不是私有的。
  • 不要这样做。您无法保证 scalac 不会在不可变对象实际上是不可变的假设下进行优化。使用copy,这就是它的用途!
  • 感谢 cmets @vptheron ,我刚刚更新了问题以添加更多详细信息来解释为什么我不想在这里使用复制方法。

标签: scala reflection


【解决方案1】:

此问题已在此处回答以帮助其他人解决此类问题。 我最终使用这段代码使用 scala 反射执行我的案例类的复制方法。

import reflect._
import scala.reflect.runtime.universe._
import scala.reflect.runtime._

class Empty

val mirror = universe.runtimeMirror(getClass.getClassLoader)
// paramName is the parameter that I want to replacte the value
// paramValue is the new parameter value
def updateParam[R : ClassTag](r: R, paramName: String, paramValue: Any): R = {

  val instanceMirror = mirror.reflect(r)
  val decl = instanceMirror.symbol.asType.toType
  val members = decl.members.map(method => transformMethod(method, paramName, paramValue, instanceMirror)).filter {
    case _: Empty => false
    case _ => true
  }.toArray.reverse

  val copyMethod = decl.declaration(newTermName("copy")).asMethod
  val copyMethodInstance = instanceMirror.reflectMethod(copyMethod)

  copyMethodInstance(members: _*).asInstanceOf[R]
}

def transformMethod(method: Symbol, paramName: String, paramValue: Any, instanceMirror: InstanceMirror) = {
  val term = method.asTerm
  if (term.isAccessor) {
    if (term.name.toString == paramName) {
      paramValue
    } else instanceMirror.reflectField(term).get
  } else new Empty
}

有了这个我可以执行我的案例类的复制方法,替换一个确定的字段值。

【讨论】:

    【解决方案2】:

    正如 cmets 所说,不要使用反射更改 val。你会用 java final 变量吗?它使您的代码做真正意想不到的事情。如果需要更改 val 的值,不要使用 val,使用 var。

    trait HasCreatedAt {
        var createdAt: Option[DateTime] = None
    }
    
    case class User(name:String) extends HasCreatedAt
    

    虽然在案例类中有一个 var 可能会带来一些意想不到的行为,例如副本不会按预期工作。这可能会导致不希望为此使用案例类。

    另一种方法是让插入方法返回案例类的更新副本,例如:

    trait HasCreatedAt {
        val createdAt: Option[DateTime]
        def withCreatedAt(dt:DateTime):this.type
    }
    
    case class User(name:String,createdAt:Option[DateTime] = None) extends HasCreatedAt {
        def withCreatedAt(dt:DateTime) = this.copy(createdAt = Some(dt))
    }
    
    trait BaseRepo[ID, R <: HasCreatedAt] {
        def insert(r: R)(implicit session: Session): (ID, R) = {
            val id = ???//insert into db
            (id, r.withCreatedAt(??? /*now*/))
        }
    }
    

    编辑:

    由于我没有回答您最初的问题,并且您可能知道自己在做什么,因此我正在添加一种方法来做到这一点。

    import scala.reflect.runtime.universe._
    
    val user = User("aaa", None)
    val m = runtimeMirror(getClass.getClassLoader)
    val im = m.reflect(user)
    val decl = im.symbol.asType.toType.declaration("createdAt":TermName).asTerm
    val fm = im.reflectField(decl)
    fm.set(??? /*now*/)
    

    但同样,请不要这样做。阅读this stackoveflow answer 以深入了解它可能导致的原因(vals 映射到最终字段)。

    【讨论】:

    • 嗨 Martin,谢谢你的回答,这个问题的目的是想知道是否有办法使用反射调用 R 的复制方法,而不是重新分配 val 的值。我没有找到复制定义,以及如何只使用一个参数来调用它,在这种情况下是 createdAt
    • 啊,对不起,我误解了这个问题。这样做似乎更困难,因为它涉及使用默认参数调用方法。这是如何完成的可以在stackoverflow.com/questions/14034142/…看到。
    猜你喜欢
    • 2020-07-10
    • 2014-07-29
    • 1970-01-01
    • 2011-01-14
    • 2010-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多