Подтвердить что ты не робот

CanEqual() в признаке scala.Equals

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

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

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

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

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

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

Мои вопросы:

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

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

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

4b9b3361

Ответ 1

Метод 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!

Чтобы исправить это, убедитесь, что два объекта имеют один и тот же (под) тип с использованием 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