Code Examples for

Programming in Scala, Second 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 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@79f0ec p2: Point = Point@1b8424e scala> val q = new Point(2, 3) q: Point = Point@d990db 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 = HashSet(p1) coll: scala.collection.mutable.HashSet[Point] = Set(Point@79f0ec) scala> coll contains p2 res2: Boolean = false
scala> val p2a: Any = p2 p2a: Any = Point@1b8424e
scala> p1 equals p2a res3: Boolean = false
def equals(other: Any): Boolean
// In file 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@67e5a7 p2: Point = Point@1165e21 scala> HashSet(p1) contains p2 res4: Boolean = false
// In file equality/Points3.scala class Point(val x: Int, val y: Int) { override def hashCode = 41 * (41 + x) + y override def equals(other: Any) = other match { case that: Point => this.x == that.x && this.y == that.y case _ => false } }
// In file equality/Points6.scala class Point(var x: Int, var y: Int) { // Problematic override def hashCode = 41 * (41 + 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@6bc scala> val coll = HashSet(p) coll: scala.collection.mutable.HashSet[Point] = Set(Point@6bc) 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 equality/Points8.scala object Color extends Enumeration { val Red, Orange, Yellow, Green, Blue, Indigo, Violet = Value }
// In file 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@6bc scala> val cp = new ColoredPoint(1, 2, Color.Red) cp: ColoredPoint = ColoredPoint@6bc scala> p equals cp res9: Boolean = true scala> cp equals p res10: Boolean = false
scala> HashSet[Point](p) contains cp res11: Boolean = true scala> HashSet[Point](cp) contains p res12: Boolean = false
// In file 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@6bc scala> val bluep = new ColoredPoint(1, 2, Color.Blue) bluep: ColoredPoint = ColoredPoint@6bc
scala> redp == p res13: Boolean = true scala> p == bluep res14: Boolean = true
scala> redp == bluep res15: Boolean = false
// In file equality/Points7.scala // A technically valid, but unsatisfying, equals method class Point(val x: Int, val y: Int) { override def hashCode = 41 * (41 + 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 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@6bc
def canEqual(other: Any): Boolean
// In file equality/Points8.scala class Point(val x: Int, val y: Int) { override def hashCode = 41 * (41 + 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 equality/Points8.scala class ColoredPoint(x: Int, y: Int, val color: Color.Value) extends Point(x, y) { override def hashCode = 41 * super.hashCode + color.hashCode 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@6bc scala> val cp = new ColoredPoint(1, 2, Color.Indigo) cp: ColoredPoint = ColoredPoint@11421 scala> val pAnon = new Point(1, 1) { override val y = 2 } pAnon: Point = $anon$1@6bc scala> val coll = List(p) coll: List[Point] = List(Point@6bc) 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 equality/Tree4.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 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@158c7fa scala> val b2 = new Branch[List[Int]](Nil, | EmptyTree, EmptyTree) b2: Branch[List[Int]] = Branch@1f4a968 scala> b1 == b2 res19: Boolean = true
// In file 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 equality/Tree3.scala override def hashCode: Int = 41 * ( 41 * ( 41 + elem.hashCode ) + left.hashCode ) + right.hashCode
// In file equality/Tree3.scala def canEqual(other: Any) = other match { case that: Branch[_] => true case _ => false }
// In file equality/Tree3.scala def canEqual(other: Any) = other.isInstanceOf[Branch[_]]
// In file 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 = 41 * ( 41 * ( 41 + elem.hashCode ) + left.hashCode ) + right.hashCode }

30.4 Recipes for equals and hashCode


// In file 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 = 41 * ( 41 + numer ) + denom override def toString = if (denom == 1) numer.toString else numer +"/"+ denom }
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
override def hashCode: Int = 41 * ( 41 * ( 41 * ( 41 * ( 41 + a.hashCode ) + b.hashCode ) + c.hashCode ) + d.hashCode ) + e.hashCode
override def hashCode: Int = 41 * ( 41 + numer ) + denom
override def hashCode: Int = 41 * ( 41 * ( super.hashCode ) + numer ) + denom
override val hashCode: Int = 41 * ( 41 + numer ) + denom

30.5 Conclusion

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

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

and:

http://booksites.artima.com/programming_in_scala_2ed

Copyright © 2007-2010 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.