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

Scala 如何在case类中定义自定义相等性,scala,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

我在下面定义了一个案例类
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
的方法是否正确。我是从。

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

override def hashCode = (i, s).##
读起来更好

为了阐明它的作用:##只是一个调用hashCode的函数,但它正确地处理null和一些与原语相关的角情况

val x: String = null
x.## // works fine. returns 0
x.hashCode // throws NullPointerException
所以(i,s)##创建i和s的元组(具有定义良好的hashCode方法),然后返回其hash代码。因此,您不必手动编写包含杂音散列等的散列代码方法。顺便说一句:如果元组的一个元素为null,这也可以正常工作,而像上面提到的那样手工编写的散列方法可能会抛出NPE


然而,根据我的经验,如果您想要修改case类为您提供的任何内容,那么您实际上并不需要case类。另外,在某些情况下,覆盖相等以不考虑某些数据似乎是一个聪明的想法,但它可能会导致一些非常混乱的行为。

对于您自己的相等版本,使用不同的运算符如何。我认为这比将
==
的默认行为重写为“近似相等”要好

编辑:

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

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")

您还可以从case类定义中删除
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

我同意“导致混乱行为”这一部分,因此我正在重新考虑是否真的可以不这样做。我可以,但我需要添加额外的逻辑来处理
Foo
中的最后一个字段。我需要权衡选项。@Jus12您可以编写
case类Foo(I:Int,s:String)(optBar:Option[Bar])
。第二个参数列表不被认为是生成的case类方法的一部分。被认为是正确答案,因为
override def hashCode=(i,s)。#
更好。你能详细说明它的作用吗?:)我在答案中加入了一些关于##的解释,我需要在收藏中使用它。例如,
listofoos.contains(foo1)
。有什么方法可以让我的用例工作吗?你可以使用
listofoos.exists(=~foo1)
,但我意识到
contains
只是一个例子,可能还有其他方法依赖于引擎盖下的
=
。如何将
i
s
放入另一个case类中,然后将其作为
Foo
的成员,这样您就可以执行类似于
listofoos.map(u.subfo).contains(foo1.subFoo)
的操作?我对错误用例的错误。正如您所说的,我们可以在
contains
等中使用它,但是我们可以在map中使用它吗,比如在
mapWithKeyFee.get(foo1)
中?我认为在这种情况下,使用两个级别的case类是有意义的,所以您可以使用
mapWithKeySubFoo.get(foo1.subFoo)
。我不知道使用Foo作为一个地图键时,它的一个参数不被认为是平等的一部分。如果你的地图是“代码类型>地图[Sufoo,无论什么] /<代码>,你尝试做<代码> map。GET(FoO)< /Cord>你应该得到编译时错误,所以应该很容易捕捉到这样的错误。我也喜欢这个,但是看起来有点尖酸刻薄。1从我这里。:)
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