Object equality has been confusing developers right since its inception (OOP).
What is object equality? Well! It has different meanings in different contexts. Lets start listing the contexts and scenarios where object equality makes sense
1) "Equality" in terms of object references.
This is the least used equality context and the scenarios include
a) When one wants to check whether two objects references point to the same object or not. If they do, then the output is a true else a false.
e.g1: val a = new Product(1,2)
val b = new Product(3,4)
If referential equality is applied between 'a' & 'b' then the output would be 'false' because 'a' & 'b' refer two different objects in memory.
Scala language has a special operator which is only used to assess referential equality between two objects. The operator is "eq" .
In the above example a eq b returns false as 'a' and 'b' point to two different objects in memory.
Lets have another example:
e.g2: val a = new String("Sandeep")
val b = new String("Sandeep")
Just like the previous example, the expression a eq b returns a false even though the String
constructor has the same string "Sandeep". This is because the 'new' operator creates a new String object every time new String() constructor is called.
Consider this example
e.g3: val a = "Sandeep"
val b = "Sandeep"
Any String is immutable is Scala, be it created using the new operator or created using the "" operator. The expression a eq b returns a true in this scenario. The reason is that when ever a String is created with the "" operator, it is internalized, meaning that the JVM keeps track of all the similar strings and stores them in a special location. The size of this location can be sent as a JVM argument using -XX:StringTableSize=n , where n is the number of bytes you want to allocate to interned strings.
Therefore, whenever the "same" new String "vals" are created using the "" operator, the JVM makes sure it points all of the references to the interned Strings. This way the memory is optimized.
Coming back to our equality discussion, in this example, since the vals 'a' & 'b' point to the same interned String, the expression a eq b returns true.
2) "Equality" in terms of natural equality
Sometimes, or even more often, we run into situations where natural equality makes more sense than anything else. The scenarios are comparing two integers, comparing two Strings, comparing floats, comparing Double or even Boolean etc. Scala has a special method "==" which is used for assessing natural equality of objects in Scala.
e.g1: val a == 1
val b == 2
The expression a == b evaluates to false
e.g2: val a = 2
val b = 2
The expression a ==b here, evaluates to true. This is because the only thing the method "==" does here is check for natural equality.
e.g3: case class Product(val x:Int, val y:Int)
val a = new Product(1,2)
val b = new Product(1,2)
The expression a==b here, evaluates to true. This operator "==" is again making a pure natural check on the objects (The object type, the values in the constructors)
e.g4: case class Product(val x:Int, val y:Int)
val a = Product(1,2)
val b = Product(3,4)
The expression a==b here, evaluates to false. This operator "==" is again making a pure natural check on the objects (The object type is same, but the values in the constructors are different, therefore returns false)
e,g5: val a = "Sandeep"
val b = "Sandeep"
The expression a==b here, evaluates to true (In JAVA it evaluates to False as there is no natural comparison happening there. We use either "equals" or "equalsIgnoreCase" methods for natural equality where Strings are used)
The "==" method is of type 'final' and therefore cannot be overridden. This is designed this way to help developers have a choice when they come across dealing with natural equality. They need not go for other methods to check for simple natural equality. The "equals" method in objects can be overridden to change the behavior of the "==". Therefore the "==" and the "equals" method are linked. By overriding "equals" method, the behavior of "==" can be changed. By doing this, we give the "==" method the equality behavior we mean, and which makes more sense to us. We will speak about the "equals" method in the next part below.
3) "Equality" in terms of the state of an object w.r.t the other
This is a more widely used and often confusing concept on object equality.
Every object (in Java & Scala) have default implementations of the method "equals". The default implementation here is nothing but a natural check on the objects (checking for its type and for its values in the constructor). But, we might want to add more to assess equality rather than just checking its type and the values in the constructor. This is where the state of the object is used to assess equality.
An example would clear the confusion here.
What is object equality? Well! It has different meanings in different contexts. Lets start listing the contexts and scenarios where object equality makes sense
1) "Equality" in terms of object references.
This is the least used equality context and the scenarios include
a) When one wants to check whether two objects references point to the same object or not. If they do, then the output is a true else a false.
e.g1: val a = new Product(1,2)
val b = new Product(3,4)
If referential equality is applied between 'a' & 'b' then the output would be 'false' because 'a' & 'b' refer two different objects in memory.
Scala language has a special operator which is only used to assess referential equality between two objects. The operator is "eq" .
In the above example a eq b returns false as 'a' and 'b' point to two different objects in memory.
Lets have another example:
e.g2: val a = new String("Sandeep")
val b = new String("Sandeep")
Just like the previous example, the expression a eq b returns a false even though the String
constructor has the same string "Sandeep". This is because the 'new' operator creates a new String object every time new String() constructor is called.
Consider this example
e.g3: val a = "Sandeep"
val b = "Sandeep"
Any String is immutable is Scala, be it created using the new operator or created using the "" operator. The expression a eq b returns a true in this scenario. The reason is that when ever a String is created with the "" operator, it is internalized, meaning that the JVM keeps track of all the similar strings and stores them in a special location. The size of this location can be sent as a JVM argument using -XX:StringTableSize=n , where n is the number of bytes you want to allocate to interned strings.
Therefore, whenever the "same" new String "vals" are created using the "" operator, the JVM makes sure it points all of the references to the interned Strings. This way the memory is optimized.
Coming back to our equality discussion, in this example, since the vals 'a' & 'b' point to the same interned String, the expression a eq b returns true.
2) "Equality" in terms of natural equality
Sometimes, or even more often, we run into situations where natural equality makes more sense than anything else. The scenarios are comparing two integers, comparing two Strings, comparing floats, comparing Double or even Boolean etc. Scala has a special method "==" which is used for assessing natural equality of objects in Scala.
e.g1: val a == 1
val b == 2
The expression a == b evaluates to false
e.g2: val a = 2
val b = 2
The expression a ==b here, evaluates to true. This is because the only thing the method "==" does here is check for natural equality.
e.g3: case class Product(val x:Int, val y:Int)
val a = new Product(1,2)
val b = new Product(1,2)
The expression a==b here, evaluates to true. This operator "==" is again making a pure natural check on the objects (The object type, the values in the constructors)
e.g4: case class Product(val x:Int, val y:Int)
val a = Product(1,2)
val b = Product(3,4)
The expression a==b here, evaluates to false. This operator "==" is again making a pure natural check on the objects (The object type is same, but the values in the constructors are different, therefore returns false)
e,g5: val a = "Sandeep"
val b = "Sandeep"
The expression a==b here, evaluates to true (In JAVA it evaluates to False as there is no natural comparison happening there. We use either "equals" or "equalsIgnoreCase" methods for natural equality where Strings are used)
The "==" method is of type 'final' and therefore cannot be overridden. This is designed this way to help developers have a choice when they come across dealing with natural equality. They need not go for other methods to check for simple natural equality. The "equals" method in objects can be overridden to change the behavior of the "==". Therefore the "==" and the "equals" method are linked. By overriding "equals" method, the behavior of "==" can be changed. By doing this, we give the "==" method the equality behavior we mean, and which makes more sense to us. We will speak about the "equals" method in the next part below.
3) "Equality" in terms of the state of an object w.r.t the other
This is a more widely used and often confusing concept on object equality.
Every object (in Java & Scala) have default implementations of the method "equals". The default implementation here is nothing but a natural check on the objects (checking for its type and for its values in the constructor). But, we might want to add more to assess equality rather than just checking its type and the values in the constructor. This is where the state of the object is used to assess equality.
An example would clear the confusion here.
e.g1:
class Product(val x:Int, y:Int) {
override equals(obj:Any):Boolean = obj match {
case obj:Product => this.x==obj.x && this.y==obj.y
case _ => false
}
}
Lets test the class by instantiating some objects
val a = new Product(1,2)
val b = new Product(2,3)
"a equals b" returns a false.
Here in this above example, there is a class by the name "Product" and which takes a constructor with values x & y. The definition of equality which we have overridden here is that the values of x and y in the callee object are checked for equality with the corresponding values of x & y respectively in the caller object. This is just a custom definition of equality of objects of type Product which we have come up with, as per our requirement.
Lets consider another example
e.g2:
import Math._
class Complex(val real:Float, imag:Float) {
override equals(obj:Any):Boolean = obj match {
case obj:Product =>sqrt (this.real*this.real + this.imag*this.imag) == sqrt (obj.real*obj.real + obj.imag*obj.imag)
case _ => false
}
}
In the above example we have a class which represents Complex numbers. The constructor takes a real part and an imaginary part as values. The definition of equality we have overridden here is that the resultant of the caller object should be same as the resultant of the callee object. RESULTANT is (Square root of the sum of the squares of the real and imaginary parts)
To test the above class for equality, lets initialize a couple
val a = new Complex(3,4)
val b = new Complex(5,0)
a equals b returns a true (even though the values in the constructors are very different). The resultant of (3,4) is 5 and the resultant of (5,0) is also 5.