Code Examples for

Programming in Scala, Fourth Edition

Return to chapter index

30 Object Equality

Sample run of chapter's interpreter examples

30.1 Equality in Scala


final def == (that: Any): Boolean = if (null eq this) {null eq that} else {this equals that}

30.2 Writing an equality method


var hashSet: Set[C] = new collection.immutable.HashSet hashSet += elem1 hashSet contains elem2 // returns false!
class Point(val x: Int, val y: Int) { ... }
// In file object-equality/Points1.scala // An utterly wrong definition of equals def equals(other: Point): Boolean = this.x == other.x && this.y == other.y
scala> val p1, p2 = new Point(1, 2) p1: Point = Point@37d7d90f p2: Point = Point@3beb846d scala> val q = new Point(2, 3) q: Point = Point@e0cf182 scala> p1 equals p2 res0: Boolean = true scala> p1 equals q res1: Boolean = false
scala> import scala.collection.mutable import scala.collection.mutable scala> val coll = mutable.HashSet(p1) coll: scala.collection.mutable.HashSet[Point] = Set(Point@37d7d90f) scala> coll contains p2 res2: Boolean = false
scala> val p2a: Any = p2 p2a: Any = Point@3beb846d
scala> p1 equals p2a res3: Boolean = false
def equals(other: Any): Boolean
// In file object-equality/Points2.scala // A better definition, but still not perfect override def equals(other: Any) = other match { case that: Point => this.x == that.x && this.y == that.y case _ => false }
def ==(other: Point): Boolean = // Don't do this!
scala> val p1, p2 = new Point(1, 2) p1: Point = Point@122c1533 p2: Point = Point@c23d097 scala> collection.mutable.HashSet(p1) contains p2 res4: Boolean = false
// In file object-equality/Points3.scala class Point(val x: Int, val y: Int) { override def hashCode = (x, y).## override def equals(other: Any) = other match { case that: Point => this.x == that.x && this.y == that.y case _ => false } }
// In file object-equality/Points5.scala class Point(var x: Int, var y: Int) { // Problematic override def hashCode = (x, y).## override def equals(other: Any) = other match { case that: Point => this.x == that.x && this.y == that.y case _ => false } }
scala> val p = new Point(1, 2) p: Point = Point@5428bd62 scala> val coll = collection.mutable.HashSet(p) coll: scala.collection.mutable.HashSet[Point] = Set(Point@5428bd62) scala> coll contains p res5: Boolean = true
scala> p.x += 1 scala> coll contains p res7: Boolean = false
scala> coll.iterator contains p res8: Boolean = true
// In file object-equality/Points8.scala object Color extends Enumeration { val Red, Orange, Yellow, Green, Blue, Indigo, Violet = Value }
// In file object-equality/Points5.scala class ColoredPoint(x: Int, y: Int, val color: Color.Value) extends Point(x, y) { // Problem: equals not symmetric override def equals(other: Any) = other match { case that: ColoredPoint => this.color == that.color && super.equals(that) case _ => false } }
scala> val p = new Point(1, 2) p: Point = Point@5428bd62 scala> val cp = new ColoredPoint(1, 2, Color.Red) cp: ColoredPoint = ColoredPoint@5428bd62 scala> p equals cp res9: Boolean = true scala> cp equals p res10: Boolean = false
scala> collection.mutable.HashSet[Point](p) contains cp res11: Boolean = true scala> collection.mutable.HashSet[Point](cp) contains p res12: Boolean = false
// In file object-equality/Points6.scala class ColoredPoint(x: Int, y: Int, val color: Color.Value) extends Point(x, y) { // Problem: equals not transitive override def equals(other: Any) = other match { case that: ColoredPoint => (this.color == that.color) && super.equals(that) case that: Point => that equals this case _ => false } }
scala> val redp = new ColoredPoint(1, 2, Color.Red) redp: ColoredPoint = ColoredPoint@5428bd62 scala> val bluep = new ColoredPoint(1, 2, Color.Blue) bluep: ColoredPoint = ColoredPoint@5428bd62
scala> redp == p res13: Boolean = true scala> p == bluep res14: Boolean = true
scala> redp == bluep res15: Boolean = false
// In file object-equality/Points7.scala // A technically valid, but unsatisfying, equals method class Point(val x: Int, val y: Int) { override def hashCode = (x, y).## override def equals(other: Any) = other match { case that: Point => this.x == that.x && this.y == that.y && this.getClass == that.getClass case _ => false } }
// In file object-equality/Points7.scala class ColoredPoint(x: Int, y: Int, val color: Color.Value) extends Point(x, y) { override def equals(other: Any) = other match { case that: ColoredPoint => (this.color == that.color) && super.equals(that) case _ => false } }
scala> val pAnon = new Point(1, 1) { override val y = 2 } pAnon: Point = $anon$1@5428bd62
def canEqual(other: Any): Boolean
// In file object-equality/Points8.scala class Point(val x: Int, val y: Int) { override def hashCode = (x, y).## override def equals(other: Any) = other match { case that: Point => (that canEqual this) && (this.x == that.x) && (this.y == that.y) case _ => false } def canEqual(other: Any) = other.isInstanceOf[Point] }
// In file object-equality/Points8.scala class ColoredPoint(x: Int, y: Int, val color: Color.Value) extends Point(x, y) { override def hashCode = (super.hashCode, color).## override def equals(other: Any) = other match { case that: ColoredPoint => (that canEqual this) && super.equals(that) && this.color == that.color case _ => false } override def canEqual(other: Any) = other.isInstanceOf[ColoredPoint] }
scala> val p = new Point(1, 2) p: Point = Point@5428bd62 scala> val cp = new ColoredPoint(1, 2, Color.Indigo) cp: ColoredPoint = ColoredPoint@e6230d8f scala> val pAnon = new Point(1, 1) { override val y = 2 } pAnon: Point = $anon$1@5428bd62 scala> val coll = List(p) coll: List[Point] = List(Point@5428bd62) scala> coll contains p res16: Boolean = true scala> coll contains cp res17: Boolean = false scala> coll contains pAnon res18: Boolean = true

30.3 Defining equality for parameterized types


// In file object-equality/Tree1.scala trait Tree[+T] { def elem: T def left: Tree[T] def right: Tree[T] } object EmptyTree extends Tree[Nothing] { def elem = throw new NoSuchElementException("EmptyTree.elem") def left = throw new NoSuchElementException("EmptyTree.left") def right = throw new NoSuchElementException("EmptyTree.right") } class Branch[+T]( val elem: T, val left: Tree[T], val right: Tree[T] ) extends Tree[T]
// In file object-equality/Tree3.scala class Branch[T]( val elem: T, val left: Tree[T], val right: Tree[T] ) extends Tree[T] { override def equals(other: Any) = other match { case that: Branch[T] => this.elem == that.elem && this.left == that.left && this.right == that.right case _ => false } }
$ fsc -unchecked Tree.scala Tree.scala:14: warning: non variable type-argument T in type pattern is unchecked since it is eliminated by erasure case that: Branch[T] => this.elem == that.elem && ^
scala> val b1 = new Branch[List[String]](Nil, | EmptyTree, EmptyTree) b1: Branch[List[String]] = Branch@9d5fa4f scala> val b2 = new Branch[List[Int]](Nil, | EmptyTree, EmptyTree) b2: Branch[List[Int]] = Branch@56cdfc29 scala> b1 == b2 res19: Boolean = true
// In file object-equality/Tree3.scala case that: Branch[t] => this.elem == that.elem && this.left == that.left && this.right == that.right
case that: Branch[t] =>
case that: Branch[_] =>
// In file object-equality/Tree3.scala override def hashCode: Int = (elem, left, right).##
// In file object-equality/Tree3.scala def canEqual(other: Any) = other match { case that: Branch[_] => true case _ => false }
// In file object-equality/Tree3.scala def canEqual(other: Any) = other.isInstanceOf[Branch[_]]
// In file object-equality/Tree4.scala class Branch[T]( val elem: T, val left: Tree[T], val right: Tree[T] ) extends Tree[T] { override def equals(other: Any) = other match { case that: Branch[_] => (that canEqual this) && this.elem == that.elem && this.left == that.left && this.right == that.right case _ => false } def canEqual(other: Any) = other.isInstanceOf[Branch[_]] override def hashCode: Int = (elem, left, right).## }

30.4 Recipes for equals and hashCode


def canEqual(other: Any): Boolean =
other.isInstanceOf[Rational]
override def equals(other: Any): Boolean =
other match { // ... }
case that: Rational =>
super.equals(that) &&
(that canEqual this) &&
numer == that.numer && denom == that.denom
case _ => false
// In file object-equality/Rational.scala class Rational(n: Int, d: Int) { require(d != 0) private val g = gcd(n.abs, d.abs) val numer = (if (d < 0) -n else n) / g val denom = d.abs / g private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) override def equals(other: Any): Boolean = other match { case that: Rational => (that canEqual this) && numer == that.numer && denom == that.denom case _ => false } def canEqual(other: Any): Boolean = other.isInstanceOf[Rational] override def hashCode: Int = (numer, denom).## override def toString = if (denom == 1) numer.toString else s"numer/denom" }
override def hashCode: Int = (a, b, c, d, e).##
override def hashCode: Int = (super.hashCode, numer, denom).##
override val hashCode: Int = (numer, denom).##

30.5 Conclusion

For more information about Programming in Scala, Fourth Edition (the "Stairway Book"), please visit:

http://www.artima.com/shop/programming_in_scala_4ed

and:

http://booksites.artima.com/programming_in_scala_4ed

Copyright © 2007-2019 Artima, Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.