scala.Equals 特征中的 canEqual()

canEqual() in the scala.Equals trait

来自源代码scala/Equals.scala (here):

package scala
trait Equals extends scala.Any {
  def canEqual(that: scala.Any): scala.Boolean
  def equals(that: scala.Any): scala.Boolean
}

在文档中,它说:

A method that should be called from every well-designed equals method that is open to be overridden in a subclass.

我随机选了一个 class 扩展 scala.Equals 并且很简单易懂。我选择了 scala.Tuple2[+T1, +T2],它扩展了特征 scala.Product[T1, T2],它又扩展了特征 scala.Product,它又扩展了特征 scala.Equals.

不幸的是,似乎因为 scala.Tuple2 案例 class,所以 canEqual()equals() 方法是自动的生成因此无法在源代码中找到 scala/Tuple2.scala (here).

我的问题是:

提前致谢!

PS:以防万一,我使用的是 Scala 2.11.7。

canEquals 方法用于覆盖 equals 应该是对称的期望 - 也就是说,如果(且仅当)a.equals(b) 为真,则 b.equals(a) 也应该是真的。将 class 的实例与子 class 的实例进行比较时,可能会出现问题。例如

class Animal(numLegs: Int, isCarnivore: Boolean) {
  def equals(other: Any) = other match {
    case that: Animal => 
      this.numLegs == that.numLegs && 
      this.isCarnivore == that.isCarnivore
    case _ => false
  }
}

class Dog(numLegs: Int, isCarnivore: Boolean, breed: String) extends Animal(numLegs, isCarnivore) {
  def equals(other: Any) = other match {
    case that: Dog => 
      this.numLegs == that.numLegs && 
      this.isCarnivore == that.isCarnivore &&
      this.breed == that.breed
    case _ => false
  }
}

val cecil = new Animal(4, true)
val bruce = new Dog(4, true, "Boxer")
cecil.equals(bruce) // true
bruce.equals(cecil) // false - cecil isn't a Dog!

要解决此问题,请在 equals 的定义中使用 canEqual 确保两个实体属于相同(子)类型:

class Animal(numLegs: Int, isCarnivore: Boolean) {
  def canEqual(other: Any) = other.isInstanceOf[Animal]
  def equals(other: Any) = other match {
    case that: Animal => 
      that.canEqual(this) &&
      this.numLegs == that.numLegs && 
      this.isCarnivore == that.isCarnivore
    case _ => false
  }
}

class Dog(numLegs: Int, isCarnivore: Boolean, breed: String) extends Animal(numLegs, isCarnivore) {
  def canEqual(other: Any) = other.isInstanceOf[Dog]
  def equals(other: Any) = other match {
    case that: Dog => 
      that.canEqual(this) &&
      this.numLegs == that.numLegs && 
      this.isCarnivore == that.isCarnivore &&
      this.breed == that.breed
    case _ => false
  }
}

val cecil = new Animal(4, true)
val bruce = new Dog(4, true, "Boxer")
cecil.equals(bruce) // false - call to bruce.canEqual(cecil) returns false
bruce.equals(cecil) // false