How to define an equals method (object equality) in Scala

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 4.15, “How to define an equals method (object equality) in Scala.”

Problem

You want to define an equals method for a Scala class so you can compare object instances to each other.

Solution

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.

These tests were created with the ScalaTest FunSuite, which is similar to writing unit tests with JUnit.

Discussion

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.

Theory

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 type Any, x.equals(x) should return true.
  • It is symmetric: for any instances x and y of type Any, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any instances x, y, and z of type AnyRef, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.

Therefore, if you override the equals method, you should verify that your implementation remains an equivalence relation.

See Also

The Scala Cookbook

This tutorial is sponsored by the Scala Cookbook, which I wrote for O’Reilly:

You can find the Scala Cookbook at these locations:

Add new comment

The content of this field is kept private and will not be shown publicly.

Anonymous format

  • Allowed HTML tags: <em> <strong> <cite> <code> <ul type> <ol start type> <li> <pre>
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.