package kapitel_09 /** * Beispiel aus * * - Algorithmen und Datenstrukturen für Dummies * - von Andreas Gogol-Döring und Thomas Letschert * - Verlag Wiley-VCH; Oktober 2019 * - Kapitel 9, Mengen und ihre Speicherung * * @author A. Gogol-Döring, Th. Letschert */ object AuD_09_04_Equals02_App extends App { /* * Case-Klassen redefinieren \textsl{equals} auf Basis der Attribute der Klasse. * Passend zu equals werden Case-Klassen auch automatisch mit einer * passenden Reimplementierung von hashCode ausgestattet. */ case class Rabbit(name: String, weight: Int) val rabbit_1 = Rabbit("Hugo", 3) val rabbit_2 = Rabbit("Hugo", 3) println(s"rabbit_1 eq rabbit_2: ${rabbit_1 eq rabbit_2}") // false (nicht identisch) println(s"rabbit_1 equals rabbit_2: ${rabbit_1 equals rabbit_2}") // true (equals wurde redefiniert) println(s"rabbit_1 == rabbit_2: ${rabbit_1 == rabbit_2}") // true (== entspricht equals) /* * Bei einer wertorientierten Case-Klasse, also einer Klasse mit unveränderlichen Objekten, muss im Allgemeinen * nichts getan werden. * * Bei einer finalen Klasse, die keine Case-Klasse ist, muss equals und hashCode definiert werden. Etwa so: */ final class Rabbit_Class (val name: String, val weight: Int) { override def equals(other: Any): Boolean = other match { case that: Rabbit => name == that.name && weight == that.weight case _ => false } override def hashCode(): Int = 31 * (weight.hashCode() + 31 * name.hashCode) } /* * Bei Klassen die nicht final sind muss darauf geachtet werden, dass die Vergleiche, bei denen Objekte aus * mehrere Ebenen der Klassenhierarchie beteiligt sind, immer noch eine Äquivalenzrelation darstellen. * Insbesondere soll gelten: * wenn x = y dann y = x * auch dann wenn $x$ und $y$ zu unterschiedlichen Klassen gehören. Beispiel: */ // OK equals und hashCode sind passend definiert class Rabbit_1 (val name: String, val weight: Int) { def canEqual(other: Any): Boolean = other.isInstanceOf[Rabbit] override def equals(other: Any): Boolean = other match { case that: Rabbit => (that canEqual this) && name == that.name && weight == that.weight case _ => false } override def hashCode(): Int = 31 * (weight.hashCode() + 31 * name.hashCode) } class Rabbit_Champion(name: String, weight: Int, val prizes: Int) extends Rabbit_1 (name: String, weight: Int) { override def canEqual(other: Any): Boolean = other.isInstanceOf[Rabbit_Champion] override def equals(other: Any): Boolean = other match { case that: Rabbit_Champion => super.equals(that) && (that canEqual this) && prizes == that.prizes case _ => false } override def hashCode(): Int = { val state = Seq(super.hashCode(), prizes) state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b) } } val egon = new Rabbit_1 ("Egon", 5) val superEgon = new Rabbit_Champion("Egon", 5, 7) println(s"\negon == superEgon: ${egon == superEgon}") // false println(s"superEgon == egon: ${superEgon == egon}") // false object GleichheitMussTransitivSein { /* * Man könnte versucht sein, beim Vergleich von zwei Objekten die \textsl{equals}-Methode der Klasse zu verwenden, * die in der Klassenhierarchie weiter oben steht. Beispielsweise: */ class Rabbit (val name: String, val weight: Int) { override def equals(other: Any): Boolean = other match { case that: Rabbit => name == that.name && weight == that.weight case _ => false } } class Rabbit_Champion(name: String, weight: Int, val prizes: Int) extends Rabbit (name: String, weight: Int) { override def equals(other: Any): Boolean = other match { case that: Rabbit_Champion => super.equals(that) && prizes == that.prizes case that: Rabbit => super.equals(that) case _ => false } } val egon = new Rabbit ("Egon", 5) val superEgon = new Rabbit_Champion("Egon", 5, 7) println(s"\negon == superEgon: ${egon == superEgon}") // true println(s"superEgon == egon: ${superEgon == egon}") // true val x = new Rabbit_Champion("Egon", 5, 7) val y = new Rabbit ("Egon", 5) val z = new Rabbit_Champion("Egon", 5, 10) def checkTransitiv(): Unit = { println("\ncheckTransitiv") println(s"x == y: ${x == y}") // true println(s"y == z: ${y == z}") // true println(s"x == z: ${x == z}") // false } /* * Um diesen Effekt zu vermeiden definiert jede Klasse, die equals redefiniert eine Methode canEqual. * so wie im Beispiel oben. */ } GleichheitMussTransitivSein.checkTransitiv() }