2015-08-19 19 views
11

z kodu źródłowego scala/Equals.scala (here):canEqual() w scala.Equals trait

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

w dokumentacji jest napisane:

Metoda, która powinna być wywołana z każdego dołka -designed to metoda, która jest otwarta do nadpisania w podklasie.

Wybrałem losowo klasę, która rozszerza się o scala.Equals i która jest dość prosta do zrozumienia. Wybrałem scala.Tuple2[+T1, +T2], która rozszerza cechę scala.Product[T1, T2], która z kolei rozszerza cechę scala.Product, która z kolei rozszerza cechę scala.Equals.

Niestety, wydaje się, że z powodu scala.Tuple2 jest klasą przypadek, metody canEqual() i equals() są generowane automatycznie, a zatem nie można było znaleźć w kodzie źródłowym scala/Tuple2.scala (here).

Moje pytania są następujące:

  • Kiedy jest dobry moment, aby przedłużyć cechę scala.Equals?
  • W jaki sposób należy wdrożyć ?
  • Jakie są najlepsze praktyki (lub wzorce) do korzystania z canEqual() w equals()?

Z góry dziękuję!

PS: Jeśli to ma znaczenie, używam Scala 2.11.7.

Odpowiedz

16

Sposób canEquals jest wykorzystywany dla oczekiwania, że ​​equals powinny być symetryczne - to znaczy, jeżeli (i tylko w przypadku) a.equals(b) jest prawdą, b.equals(a) powinny być prawdziwe. Problemy z tym mogą wystąpić podczas porównywania instancji klasy z instancją podklasy. Na przykład.

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! 

Aby rozwiązać ten problem, należy upewnić się, że dwa podmioty są tego samego (pod) wpisać używając canEqual w definicji 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 
+1

Dzięki za odpowiedź! Myślę, że trik polega na 'that.canEqual (this)' zamiast 'this.canEqual (that)', który sprawdza "w przeciwny sposób", aby zapewnić poprawność 'equals()'. Czy wiesz, czy wygenerowana wersja klasy spraw ma taki sam wzór, jak tutaj pokazano? Dzięki! –

+0

@ SiuChingPong-AsukaKenji- Wierzę, że tak, ale nie wiem na pewno. – Shadowlands