1. Overview
In this tutorial, we’re going to review the concept of equality in Scala. We’ll also do some comparing to how equality works in Java.
2. The Operators == and !=
In Scala, single-parameter methods support infix notation, so often we refer to them as operators.
For instance, == and != are just the methods of class Any, the root of the Scala class hierarchy. They take another instance of Any as a parameter and return a Boolean result. Since != is just a negation of ==, it’s sufficient to understand how the equality operator works.
For AnyVal instances, which correspond to Java primitive types, the operator compares whether two primitives are the same, just as it does with Java:
val intAnyVal = 4
assert(intAnyVal == 2 * 2)
For referential types, the situation is different. Unlike its Java counterpart, the equality operator does not compare corresponding references but turns into a null-safe equals() call for instances of AnyRef:
val firstString = new String("AnyRef")
val secondString = new String("AnyRef")
val thirdString = null
val fourthString = null
assert(firstString == secondString)
// Unlike in java, the following lines of code do not cause NullPointerExceptions
assert(thirdString != secondString)
assert(fourthString == thirdString)
Though it isn’t a Scala-idiomatic approach, explicit calls to equals() are also permissible:
val firstString = new String("AnyRef")
val secondString = new String("AnyRef")
assert(firstString.equals(secondString))
3. The Operators eq and ne
The overridden versions of equals() define a concept of structural equality.
However, sometimes we need to check a stronger condition, like whether two references point to the same object in a heap. This is known as referential equality.
The operator eq and its negation counterpart ne serve this purpose in Scala:
val firstString = new String("AnyRef")
val secondString = new String("AnyRef")
val thirdString = secondString
assert(firstString ne secondString)
assert(thirdString eq secondString)
// Both operators are null-safe
assert(null eq null)
assert(null ne firstString)
4. equals() and hashcode() Overriding
Though type inference and syntactic sugar make Scala less verbose than Java, there’s no proper equality semantics working out of the box.
Unless we define a case class, where appropriate equals() and hashcode() versions are generated by the compiler, it’s our responsibility to override them in a regular Scala class. Otherwise, equality might not work as we would expect it to.
To illustrate that, let’s define a simple class:
class PersonSimpleClass(val name: String, val age: Int)
Since we don’t override the method equals(), equality won’t work for PersonSimpleClass instances:
val firstSimpleClassInstance = new PersonSimpleClass("Donald", 66)
val secondSimpleClassInstance = new PersonSimpleClass("Donald", 66)
assert(firstSimpleClassInstance != secondSimpleClassInstance)
So to fix that, let’s create a class with properly overridden methods:
class PersonClassWithOverrides(val name: String, val age: Int) {
override def equals(other: Any): Boolean = other match {
case person: PersonClassWithOverrides =>
this.name == person.name && this.age == person.age
case _ => false
}
override def hashCode(): Int = if (name eq null) age else name.hashCode + 31 * age
}
Note that the PersonSecond equals() implementation is quite compact in comparison with Java analogs. Mainly, it’s a result of pattern matching and the equality operator null-safety. In this case, the operator == works as expected:
val firstClassWithOverridesInstance = new PersonClassWithOverrides("Donald", 66)
val secondClassWithOverridesInstance = new PersonClassWithOverrides("Donald", 66)
assert(firstClassWithOverridesInstance == secondClassWithOverridesInstance)
Finally, let’s demonstrate the simplest way to handle equality issues and define a case class:
case class PersonCaseClass(name: String, age: Int)
Besides other useful features, case classes have hashcode() and equals() implementations generated by the Scala compiler. So, we can safely use them when testing for equality:
val firstCaseClassInstance = PersonCaseClass("Donald", 66)
val secondCaseClassInstance = PersonCaseClass("Donald", 66)
assert(firstCaseClassInstance == secondCaseClassInstance)
5. Conclusion
In this article, we looked at equality in Scala and have listed the related features for a seamless transition from Java.
As always, all of the code in this article is available on GitHub.