Defining 'Object's equality'
One critical reason to prefer immutability is the ease of equality implementation.
class Point2(var x:Int, var y:Int) {
def move (mx:Int,my:Int): Unit = {
x = x + mx
y = y+ my
}
override def hashCode:Int = y + (31 * x)
}
Let's suppose we want to map each point to some name.
val x = new Point2(1,1)
val y = new Point2(2,1)
val map = HashMap(x -> "HAI", y -> "ZOMG")
val z = new Point2(1,1)
map(z) //java.util.NoSuchElementException
This is hapenning since we did not implement our own equals method, and as in Java
the default equality of instances is checked by their address in memory.
Since Scala's AnyVal has no equivalent in Java as appose to AnyRef which is just
an alias to java.lang.Object, Scala has it's own convention for hashCode and equlas
which unifies all types:
The == an alias method for equals
The ## an alias method for hashCode
Let's implement our one equals
class Point2(var x:Int, var y:Int) {
def move(mx: Int, my: Int) : Unit = {
x = x + mx
y = y + my
}
override def hashCode(): Int = y + (31*x)
def canEqual(that: Any): Boolean = that match {
case p: Point2 => true
case _ => false
}
override def equals(that: Any): Boolean = {
def strictEquals(other: Point2) =
this.x == other.x && this.y == other.y
that match {
case a: AnyRef if this eq a => true
case p: Point2 => (p canEqual this) && strictEquals(p)
case _ => false
}
}
}
In this case we will have a problem if after inserting an object into a Map
we will modify it's fields. This way we will change the outcome of it's ## method
and thus won't ffind it in the Map
Thus:
* If two objects are equal, they should have the same hashCode.
* A hashCode computed for an object won’t change for the life of the object.
* When sending an object to another JVM, equality should be determined using
attributes available in both JVMs.
Polymorpfhic Equality:
* When we have a polymorphic object with more than one level of concrete implementations
it is harder to implement the equlas method the right way.
Example:
trait InstanteneousTime {
val repr:Int //number representing seconds since epoch start
override def equals(other:Any) = other match {
case that:InstanteneousTime =>
if(this eq that)
true
else {
that.## == this.## && //A good equals method uses hashCode for an early equality condition
repr == that.repr
}
case _ => false
}
override def hashCode():Int = repr.##
}
trait Event extends InstanteneousTime {
val name:String
override def equals(other:Any):Boolean = other match {
case that:Event =>
if(this eq that)
true
else {
repe == that.repr && name == that.name
}
case _ => false
}
}
val x = new InstanteneousTime {var repr = 2}
val y = new Event {val name = "Test Event"; val repr = 2}
y == x //false
x == y //true
So the right implementation is as follows:
trait InstantaneousTime extends Equals {
val repr: Int
override def canEqual(other: Any) = other.isInstanceOf[InstantaneousTime]
override def equals(other: Any) : Boolean = other match {
case that: InstantaneousTime =>
if(this eq that) true else {
(that.## == this.##) &&
(that canEqual this) &&
(repr == that.repr)
}
case _ => false
}
override def hashCode(): Int = repr.hashCode
}
trait Event extends InstaneousTime {
val name: String
override def canEqual(other:Any) = other.isInstanceOf[Event]
override def equals(other:Any) = other match {
case that: Event =>
if(this eq that)
true
else {
(that canEqual this) &&
(repr == that.repr) &&
(name == taht.name)
}
case _ => false
}
}
* When overriding equality of a parent class, also override canEqual