canEqual () в scala.Равна черта

из исходного кода scala/Equals.scala (здесь):

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

в документации говорится:

метод, который должен вызываться из каждого хорошо разработанного метода equals, открытого для переопределения в подклассе.

я случайно выбрал класс, который расширяется scala.Equals и который достаточно прост, чтобы понять. Я выбрал scala.Tuple2[+T1, +T2], что расширяет признак scala.Product[T1, T2], что в свою очередь расширяет черта scala.Product, который в поворот расширяет признак scala.Equals.

к сожалению, кажется, что потому что scala.Tuple2 это класс case, the canEqual() и equals() методы генерируются автоматически и поэтому не могут быть найдены в исходном коде scala/Tuple2.scala (здесь).

мои вопросы:

  • когда это хорошее время, чтобы расширить черта scala.Equals?
  • как следует ?
  • что самое лучшее практика (или шаблон) для использования canEqual() на equals()?

спасибо заранее!

PS: Если это имеет значение, я использую Scala 2.11.7.

1 ответов


The canEquals метод используется для покрытия ожидания, что equals должен быть симметричным - то есть, если (и только если) a.equals(b) истинно, то b.equals(a) также должно быть истинным. Проблемы с этим могут возникнуть при сравнении экземпляра класса с экземпляром подкласса. Например.

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!

чтобы исправить это, убедитесь, что два объекта имеют один и тот же (sub-)тип, используя canEqual в определении equals:

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