Code Examples for

Programming in Scala

Return to chapter index

28 Object Equality

Sample run of chapter's interpreter examples

28.1 Equality in Scala


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

28.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@62d74e p2: Point = Point@254de0 scala> val q = new Point(2, 3) q: Point = Point@349f8a 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.Set[Point] = Set(Point@62d74e) scala> coll contains p2 res2: Boolean = false
scala> val p2a: Any = p2 p2a: Any = Point@254de0
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@670f2b p2: Point = Point@14f7c0 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@2b scala> val coll = HashSet(p) coll: scala.collection.mutable.Set[Point] = Set(Point@2b) scala> coll contains p res5: Boolean = true
scala> p.x += 1 scala> coll contains p res7: Boolean = false
scala> coll.elements 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@2b scala> val cp = new ColoredPoint(1, 2, Color.Red) cp: ColoredPoint = ColoredPoint@2b scala> p equals cp res8: Boolean = true scala> cp equals p res9: Boolean = false
scala> HashSet[Point](p) contains cp res10: Boolean = true scala> HashSet[Point](cp) contains p res11: 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@2b scala> val bluep = new ColoredPoint(1, 2, Color.Blue) bluep: ColoredPoint = ColoredPoint@2b
scala> redp == p res12: Boolean = true scala> p == bluep res13: Boolean = true
scala> redp == bluep res14: 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@2b
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 res0: Boolean = true scala> coll contains cp res1: Boolean = false scala> coll contains pAnon res2: Boolean = true

28.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@2f1eb9 scala> val b2 = new Branch[List[Int]](Nil, | EmptyTree, EmptyTree) b2: Branch[List[Int]] = Branch@be55d1 scala> b1 == b2 res0: 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 }

28.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

28.5 Conclusion

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

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

and:

http://booksites.artima.com/programming_in_scala

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