2015-08-19 34 views
11

Dal codice sorgente scala/Equals.scala (here):canEqual() nei scala.Equals tratto

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

Nella documentazione, che dice:

Un metodo che dovrebbe essere chiamato da ogni bene -progetto metodo uguale che è aperto per essere sovrascritto in una sottoclasse.

Ho scelto a caso una classe che si estende scala.Equals e che è abbastanza semplice da capire. Ho scelto scala.Tuple2[+T1, +T2], che estende il tratto scala.Product[T1, T2], che a sua volta estende il tratto scala.Product, che a sua volta estende il tratto scala.Equals.

Purtroppo, sembra che causa scala.Tuple2 è una classe caso, le canEqual() e equals() metodi sono generati automaticamente e pertanto non può essere trovato nel codice sorgente scala/Tuple2.scala (here).

Le mie domande sono:

  • Quando è un buon momento per estendere il tratto scala.Equals?
  • Come deve essere implementato canEqual()?
  • Quali sono le migliori pratiche (o standard) per utilizzare canEqual() in equals()?

Grazie in anticipo!

PS: In caso se è importante, sto usando Scala 2.11.7.

risposta

16

Il metodo canEquals viene utilizzato per coprire l'aspettativa che equals dovrebbe essere simmetrica - cioè, se (e solo se) a.equals(b) è vero, allora b.equals(a) dovrebbe essere anche vero. I problemi con questo possono sorgere quando si confronta un'istanza di una classe con un'istanza di una sottoclasse. Per esempio.

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! 

Per risolvere il problema, assicurarsi che le due entità sono dello stesso (sotto) tipo utilizzando canEqual nella definizione di 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

Grazie per la risposta! Penso che il trucco sia 'that.canEqual (this)' invece di 'this.canEqual (that)', che controlla "in modo opposto" per garantire la validità di 'equals()'. Sai se la versione case class generata segue lo stesso schema che hai mostrato qui? Grazie! –

+0

@ SiuChingPong-AsukaKenji- Lo credo, ma non lo so per certo. – Shadowlands