You want to define an equals
method for your class so you can compare object instances to each
other.
Like Java, you define an equals
method (and hashCode
method) in your
class to compare two instances, but unlike Java, you then use the
==
method to compare the equality of
two instances.
There are many ways to write equals
methods. The following example shows
one possible way to define an equals
method and its corresponding hashCode
method:
class
Person
(
name
:
String
,
age
:
Int
)
{
def
canEqual
(
a
:
Any
)
=
a
.
isInstanceOf
[
Person
]
override
def
equals
(
that
:
Any
)
:
Boolean
=
that
match
{
case
that
:
Person
=>
that
.
canEqual
(
this
)
&&
this
.
hashCode
==
that
.
hashCode
case
_
=>
false
}
override
def
hashCode
:
Int
=
{
val
prime
=
31
var
result
=
1
result
=
prime
*
result
+
age
;
result
=
prime
*
result
+
(
if
(
name
==
null
)
0
else
name
.
hashCode
)
return
result
}
}
This example shows a modified version of a hashCode
method that Eclipse generated for a
similar Java class. It also uses a canEqual
method, which will be explained
shortly.
With the equals
method defined,
you can compare instances of a Person
with ==
, as demonstrated in the
following tests:
import
org.scalatest.FunSuite
class
PersonTests
extends
FunSuite
{
// these first two instances should be equal
val
nimoy
=
new
Person
(
"Leonard Nimoy"
,
82
)
val
nimoy2
=
new
Person
(
"Leonard Nimoy"
,
82
)
val
shatner
=
new
Person
(
"William Shatner"
,
82
)
val
ed
=
new
Person
(
"Ed Chigliak"
,
20
)
// all tests pass
test
(
"nimoy == nimoy"
)
{
assert
(
nimoy
==
nimoy
)
}
test
(
"nimoy == nimoy2"
)
{
assert
(
nimoy
==
nimoy2
)
}
test
(
"nimoy2 == nimoy"
)
{
assert
(
nimoy2
==
nimoy
)
}
test
(
"nimoy != shatner"
)
{
assert
(
nimoy
!=
shatner
)
}
test
(
"shatner != nimoy"
)
{
assert
(
shatner
!=
nimoy
)
}
test
(
"nimoy != null"
)
{
assert
(
nimoy
!=
null
)
}
test
(
"nimoy != String"
)
{
assert
(
nimoy
!=
"Leonard Nimoy"
)
}
test
(
"nimoy != ed"
)
{
assert
(
nimoy
!=
ed
)
}
}
As noted in the code comments, all of these tests pass.
Note
These tests were created with the ScalaTest FunSuite
, which is similar to writing unit
tests with JUnit.
The first thing to know about Scala and the equals
method is that, unlike Java, you
compare the equality of two objects with ==
. In Java, the ==
operator compares “reference equality,” but
in Scala, ==
is a method you use on
each class to compare the equality of two instances, calling your
equals
method under the
covers.
As mentioned, there are many ways to implement equals
methods, and the code in the Solution
shows just one possible approach. The book Programming in
Scala contains one chapter of more than 25 pages on “object
equality,” so this is a big topic.
An important benefit of the approach shown in the Solution is that
you can continue to use it when you use inheritance in classes. For
instance, in the following code, the Employee
class extends the Person
class that’s shown in the
Solution:
class
Employee
(
name
:
String
,
age
:
Int
,
var
role
:
String
)
extends
Person
(
name
,
age
)
{
override
def
canEqual
(
a
:
Any
)
=
a
.
isInstanceOf
[
Employee
]
override
def
equals
(
that
:
Any
)
:
Boolean
=
that
match
{
case
that
:
Employee
=>
that
.
canEqual
(
this
)
&&
this
.
hashCode
==
that
.
hashCode
case
_
=>
false
}
override
def
hashCode
:
Int
=
{
val
ourHash
=
if
(
role
==
null
)
0
else
role
.
hashCode
super
.
hashCode
+
ourHash
}
}
This code uses the same approach to the canEqual
, equals
, and hashCode
methods, and I like that consistency.
Just as important as the consistency is the accuracy of the approach,
especially when you get into the business of comparing instances of a
child class to instances of any of its parent classes. In the case of
the Person
and Employee
code shown, these classes pass all of
the following tests:
class
EmployeeTests
extends
FunSuite
with
BeforeAndAfter
{
// these first two instance should be equal
val
eNimoy1
=
new
Employee
(
"Leonard Nimoy"
,
82
,
"Actor"
)
val
eNimoy2
=
new
Employee
(
"Leonard Nimoy"
,
82
,
"Actor"
)
val
pNimoy
=
new
Person
(
"Leonard Nimoy"
,
82
)
val
eShatner
=
new
Employee
(
"William Shatner"
,
82
,
"Actor"
)
test
(
"eNimoy1 == eNimoy1"
)
{
assert
(
eNimoy1
==
eNimoy1
)
}
test
(
"eNimoy1 == eNimoy2"
)
{
assert
(
eNimoy1
==
eNimoy2
)
}
test
(
"eNimoy2 == eNimoy1"
)
{
assert
(
eNimoy2
==
eNimoy1
)
}
test
(
"eNimoy != pNimoy"
)
{
assert
(
eNimoy1
!=
pNimoy
)
}
test
(
"pNimoy != eNimoy"
)
{
assert
(
pNimoy
!=
eNimoy1
)
}
}
All the tests pass, including the comparison of the eNimoy
and pNimoy
objects, which are instances of the
Employee
and Person
classes, respectively.
The Scaladoc for the equals
method of the Any
class states,
“any implementation of this method should be an equivalence
relation.” The documentation states that an equivalence
relation should have these three properties:
It is reflexive: for any instance
x
of typeAny
,x.equals(x)
should return true.It is symmetric: for any instances
x
andy
of typeAny
,x.equals(y)
should return true if and only ify.equals(x)
returns true.It is transitive: for any instances
x
,y
, andz
of typeAnyRef
, ifx.equals(y)
returns true andy.equals(z)
returns true, thenx.equals(z)
should return true.
Therefore, if you override the equals
method, you should verify that your
implementation remains an equivalence relation.
The Artima website has an excellent related article titled How to Write an Equality Method in Java.
Eric Torreborre shares an excellent
canEqual
example on GitHub.“Equivalence relation” defined on Wikipedia.
The Scala
Any
class.
Get Scala Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.