简体   繁体   English

Scala中运行时的结构化等于

[英]Structured equals at runtime in Scala

Is there some function in scala that will compare two objects structurally at runtime? scala中是否有一些函数可以在运行时从结构上比较两个对象? I'd rather not have to manually create an equals function due to the number of fields. 由于字段数量的原因,我不必手动创建等于函数。 This is for a unit test, so performance isn't an issue. 这是一个单元测试,因此性能不是问题。 I just need something that reflectively looks through the fields and compares the values. 我只需要一些反射性地透过字段并比较值的东西。

Background: I have a case class that extends from a trait that overrides equals and thus doesn't generate the structural equals (squeryl KeyedEntity, related: Prevent Mixin overriding equals from breaking case class equality ), and I'm not in a position to change that right now. 背景:我有一个案例类,它从一个覆盖等于的特征延伸,因此不会生成结构等于(squeryl KeyedEntity,相关: 防止Mixin重写等于破坏案例类相等 ),我不能够现在改变它。 This is for a unit test so both instances are not persisted. 这是针对单元测试的,因此两个实例都不会保留。

AFAIK the Scala standard library does not contain such a function. AFAIK Scala标准库不包含这样的功能。 But you have two options. 但是你有两种选择。

  1. You write a macro, which generates a method to compare equality of two given values 您编写一个宏,它生成一个比较两个给定值的相等性的方法
  2. Use Java reflection if performance does not matter. 如果性能无关紧要,请使用Java反射。 See this answer: Is there a Java reflection utility to do a deep comparison of two objects? 看到这个答案: 是否有一个Java反射实用程序来对两个对象进行深度比较?

I came up with this solution which iterates through all fields, and for private fields, finds a corresponding method in scala. 我想出了这个迭代遍历所有字段的解决方案,对于私有字段,在scala中找到相应的方法。 Indeed, case classes have private members accessible through methods of the same name. 实际上,案例类的私有成员可以通过相同名称的方法访问。

implicit class ReallyEqual[T](a: T) {
  import java.lang.reflect.Modifier._
  def ==:==[U](b: U): Boolean = {
    val fieldsA = a.getClass.getDeclaredFields
    val methodsA = a.getClass.getDeclaredMethods
    val mapA = (methodsA.toList map (z => (z.getName(), z))).toMap
    val fieldsB = b.getClass.getDeclaredFields
    val methodsB = b.getClass.getDeclaredMethods
    val mapB = (methodsB.toList map (z => (z.getName(), z))).toMap
    fieldsA.length == fieldsB.length &&
    (true /: (fieldsA zip fieldsB)){ case (res, (aa, bb)) =>
      if(((aa.getModifiers & STATIC) != 0) || ((bb.getModifiers & STATIC) != 0)) { res  // ignore
      } else if(((aa.getModifiers & (PRIVATE | PROTECTED)) == 0) && ((bb.getModifiers & (PRIVATE | PROTECTED)) == 0)) {
        res && aa == bb && aa.get(a) ==:== bb.get(b)
      } else if((mapA contains aa.getName) && (mapB contains bb.getName)) {
        res && mapA(aa.getName).invoke(a) == mapB(aa.getName).invoke(b)
      } else res
    }
  }
}

trait EqualBad {
  override def equals(other: Any) = false
}

case class MyCaseClass(x: Int, y: List[Int]) extends EqualBad

MyCaseClass(1, List(1, 2)) ==:== MyCaseClass(1, List(1, 2)) // true
MyCaseClass(1, List(1, 2)) ==:== MyCaseClass(1, List(2, 2)) // false
MyCaseClass(1, List(1, 2)) ==:== MyCaseClass(2, List(1, 2)) // false
MyCaseClass(1, List(1, 2)) == MyCaseClass(1, List(1, 2))  // false

You can even make it more recursive by changing the last == in the method ReallyEqual to ==:== 您甚至可以通过将方法ReallyEqual为==:==来使其更加递归

Careful : This will not work for integers, because Int does not have any field or methods. 小心 :这对整数不起作用,因为Int没有任何字段或方法。 Ex: 1 ==:== 2 will return true. 例:1 ==:== 2将返回true。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM