如何在case类中定义自定义相等

时间:2015-08-10 08:36:56

标签: scala

我在下面定义了一个案例类Foo。我想覆盖==的行为,以便在比较中忽略最后一个元素(optBar)。这是我尝试过的,似乎有效。

case class Bar(i:Int)
case class Foo(i:Int, s:String, optBar:Option[Bar]) {
    override def equals(o:Any) = o match {
        case Foo(`i`, `s`, _) => true
        case _ => false
    }
    override def hashCode = i.hashCode*997  ^ s.hashCode * 991
}
val b = Bar(1)
val f1 = Foo(1, "hi", Some(b))
val f2 = Foo(1, "hi", None)
f1 == f2 // true

我想知道的是创建hashCode的方法是否正确。我是从this link得到的。

3 个答案:

答案 0 :(得分:11)

您的hashCode定义是正确的,因为它符合equals / hashCode合约。但我认为

override def hashCode = (i, s).##

更好阅读。

澄清它的作用:##只是调用hashCode的convenience method on scala.Any,但正确处理null和与基元相关的一些极端情况。

val x: String = null
x.## // works fine. returns 0
x.hashCode // throws NullPointerException

所以(i,s)。##创建了一个i和s的元组(它有一个定义良好的hashCode方法),然后返回它的哈希码。因此,您不必手动编写涉及MurmurHash等的哈希代码方法。顺便说一下:如果元组的其中一个元素为null,这也将正常工作,而手写的哈希方法就像问题中的那个可能会抛出NPE。

但是,根据我的经验,如果您想修改案例类为您提供的任何内容,您实际上并不需要案例类。此外,在不考虑某些数据的情况下重写相等性可能在某些方面似乎是一个聪明的想法,但它可能会导致一些非常混乱的行为。

答案 1 :(得分:1)

如何使用不同的运算符来实现您自己的相等版本。我认为这比覆盖==的默认行为更好,例如~=为“大致相等”

case class Bar(i:Int)
case class Foo(i:Int, s:String, optBar:Option[Bar]) {

  def ~= (that:Foo): Boolean = (this.i, this.s) == (that.i, that.s)

}

val foo1 = Foo(1, "a", None)

val foo2 = Foo(1, "a", Some(Bar(4)))

foo1 == foo2 //false

foo1 ~= foo2 //true

编辑:

如果您希望能够将其用作Map键,那么我会尝试:

case class Bar(i: Int)

trait FooLike {
  def s: String
  def i: Int

  def ~=(that: FooLike) = (s, i) == (that.s, that.i) 
}

case class SubFoo(s: String, i: Int) extends FooLike

case class Foo(sub: SubFoo, barOpt: Option[Bar]) extends FooLike {
  def s = sub.s
  def i = sub.i
}

val map = scala.collection.mutable.Map.empty[SubFoo, String]

val sub = SubFoo("a", 1)

val foo = Foo(sub, None)

foo ~= sub //true

val foo2 = Foo(sub, Some(Bar(1)))

foo ~= foo2 ///true

map += sub -> "abc"

map.get(foo.sub) //Some("abc")

答案 2 :(得分:1)

您还可以从案例类定义中删除optBar,并使用三个参数创建构造函数。要避免在使用该构造函数时必须使用new关键字,可以创建一个伴随对象。

case class Bar(i:Int)
case class Foo(i:Int, s:String) {
  var optBar: Option[Bar] = None

  def this(i:Int, s:String, optBar:Option[Bar]) {
    this(i, s)
    this.optBar = optBar
  }
}
object Foo {
  def apply(i:Int, s:String, optBar:Option[Bar]) =
    new Foo(i, s, optBar)
}

val b = Bar(1)
val f1 = Foo(1, "hi", Some(b))
val f2 = Foo(1, "hi", None)
f1 == f2 // true