データ構造をMapなどに格納する場合、hashCodeequalsを適切に定義しないと、keyによる検索が上手くいかない。

object Point {
	def apply(x:Int, y:Int) = new Point(x, y)
}

class Point(val x:Int, val y:Int) {
	// Add and multiply by prime numbers
	override def hashCode = (x + 31) * 31 + y 

    override def equals(other:Any) = other match {
		case that: Point =>
			(that canEqual this) && (this.x == that.x) && (this.y == that.y)
		case _ => false
	}
	// Pointを継承した他のクラスのインスタンスでないかチェック
	def canEqual(other:Any) = other.isInstanceOf[Point]
}

以上のようにhashCode, equalsの定義をするとMapに格納されたkeyを検索できるようになる。

val m = Map(Point(1, 3) -> "A", Point(5, 5) -> "B")
val v = m(Point(1,3))   // "A" が見つかる

継承されたクラスとの比較

また、canEqualの部分でのチェックは、例えば以下のようにPointを拡張したPointWithColorを作成した場合、PointクラスのインスタンスとPointWithColorのインスタンスを誤って同一視しないために必要。

class PointWithColor(x:Int, y:Int, val color:String) extends Point(x, y)

もし上のコードからcanEqualのチェック部分を取り除くと、

val p = new Point(1, 1)
val c = new PointWithColor(1, 1, "red")
p == c // trueになってしまう!!

参照(reference)として比較

==では、equalsのメソッドを用いてオブジェクトの比較がなされるが、eqは参照としての比較がなされる。参照先が同じインスタンスを指す場合、eqによる比較はtrueを返す。

scala> val p1 = new Point(1, 1)
p1: Point = Point@6bb

scala> val p2 = new Point(1, 1)
p2: Point = Point@6bb

scala> p1 == p2
res37: Boolean = true

scala> p1 eq p2
res38: Boolean = false

scala> val p3 = p1
p3: Point = Point@6bb

scala> p3 eq p1
res39: Boolean = true

関連文献

より良いhash関数の計算に関してはUniversal hashingを参照のこと。

comments powered by Disqus