Object Equality in Scala

5,421 views

Published on

Object Equality In Scala, means comparing two objects by their values and references.Generally, equality is ubiquitous in programming. It is also more tricky than it looks at first glance. This presentation looks at object equality in detail and gives some recommendations to consider when we design our own equality tests.

Published in: Technology
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
5,421
On SlideShare
0
From Embeds
0
Number of Embeds
627
Actions
Shares
0
Downloads
29
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

Object Equality in Scala

  1. 1. Object Equality In Scala Ruchi Agarwal Software Consultant Knoldus
  2. 2. Object Equality In Java & Scala:- Two equality comparisons: 1. == Operator 2. Equals()- The == operator, which is the natural equality for value types and object indentity for Refernce types in Java.// This is Javaboolean isEqual(int x, int y){ return x == y; // Natural equality on value types}System.out.println(isEqual ( 421, 421)); //Output: True//java.lang.Integer(Object)boolean isEqual(Integer x, Integer y){ return x == y; // Reference equality on reference types}System.out.println(isEqual ( 421, 421)); //Output: False
  3. 3. - The == equality is reserved in Scala for the “ Natural ” equality of each type. The equality operation == in scala is designed to be transparent with respect to the types representation.- For value types, it is the natural (numeric or boolean) equality.- For reference types, == is treated as an alias of the equals() inherited from object.//This is Scalascala> def isEqual(x : Int , y : Int) = x == yisEqual: (x: Int, y: Int )Booleanscala> isEqual( 421 , 421)res8: Boolean = truescala> def isEqual(x : Any , y : Any) = x == yisEqual: (x: Any , y: Any)Booleanscala> isEqual( 421 , 421)res8: Boolean = true
  4. 4. String Comparisons:- Scala, never fall into Javas well-known trap concerning string comparisons.//This is Scalascala> val x=“abcd”.substring(2)x: java.lang.String = cdscala> val y=“abcd”.substring(2)y: java.lang.String = cdscala> x == yres7: Boolean = true//This is Javaboolean isEquals(String x, String y){ return x.substring(2) == y.substring(2); //return x.substring(2).equals(y.substring(2)); //Output: true}String s1="abcd";String s2="abcd";System.out.println(isEquals(s1, s2)); //Output: false
  5. 5. Reference Equality:- For refernce equality, Scalas class AnyRef defines an additional eq method, which cannot be overridden and is implemented as reference equality(i. e., it behaves like == in Java for reference types).scala> val x=new String(“abc”)x: java.lang.String = abcscala> val x=new String("abc")x: java.lang.String = abcscala> x == yres13: Boolean = true // Value equalityscala> x eq yres14: Boolean = false // Reference Equality- Theres also the negation of eq , which is called ne. scala> x ne yres14: Boolean = true
  6. 6. - For refernce equality, the behavior of == for new types can redefine by overriding the equals method, which is always inherited from class Any.- It is not possible to override == directly, as it is defined as a final method in class Any.// defination of == in Any classfinal def == (that : Any) : Boolean = if ( null eq this) { null eq that } else { this equals that }
  7. 7. Writing an Equality Method: - Writing a correct equality method is surprisingly difficult in object-oriented languages. - This is problematic, because equality is at the basis of many other things. - Here are four common pitfalls that can cause inconsistent behavior when overriding equals: 1. Defining equals with wrong signature 2. Changing equals without also changing hashCode 3. Defining equals in terms of mutable fields 4. Failing to define equals as an equivalence relation
  8. 8. Pitfall #1: Defining equals with wrong signatureclass Point ( val x:Int, val y :Int) { def equals (other: Point): Boolean = //Overloaded equals() this.x == other.x && this.y == other.y }scala> val p1, p2 = new Point (1 , 2)p1: Point = Point@1e0bb90p2: Point = Point@139fb49scala> val q= new Point( 2 , 3)q: Point = Point@a44ec3//Working OKscala> p1 equals p2 res:0 Boolean : truescala> p1 equals q res:0 Boolean : false//Troublescala> val p2a: Any = p2 // Alias of p2p2a: Any = Point@139fb49// p2a calling equals() of Any classscala> p1 equals p2a res:0 Boolean : false
  9. 9. Solution: Correct Signature class Point ( val x:Int, val y :Int) { // A better definition:Overriding equals of Any class override def equals(other: Any) = other match { case that: Point => this.x == that.x && this.y == that.y case _ => false } } scala> val p2a: Any = p2 // Alias of p2 p2a: Any = Point@139fb49 scala> p1 equals p2a res:0 Boolean : true
  10. 10. - A related pitfall is to define == with a wrong signature.- If try to redefine == with the correct signature, which takes an argument of type Any, the compiler will give an error because you try to override a final method of type Any.- However, newcomers to Scala sometimes make two errors at once: They try to override == and they give it the wrong signature.- For instance: def ==(other: Point): Boolean = // Don’t do this! (User-defined == method)- In that case, the user-defined == method is treated as an overloaded variant of the same-named method class Any, and the program compiles.- However, the behavior of the program would be just as dubious as if you had defined equals with the wrong signature.
  11. 11. Pitfall #2: Changing equals without also changing hashCode //Previously defined equals method class Point ( val x:Int, val y :Int) { // A better definition:Overriding equals of Any class override def equals(other: Any) = other match { case that: Point => this.x == that.x && this.y == that.y case _ => false } } scala> import scala.collection.mutable._ import scala.collection.mutable._ scala> val p1, p2 = new Point (1 , 2) p1: Point = Point@1bef480 p2: Point = Point@1a6068c //storing p1 to collection scala> val coll = HashSet (p1) coll: scala.collection.mutable.Set[Point] = Set(Point@62d74e) scala> coll contains p2 res2: Boolean = false
  12. 12. - In fact, this outcome is not 100% certain. We might also get true from the experiment. We can try with some other points with coordinates 1 and 2. Eventually, we’ll get one which is not contained in the set. hashSet(p1) contains p2 val p2 true val p1 val p2 false hashSet Hash Bucket Hash Bucket Pitfall #2: Changing equals without also changing hashCode
  13. 13. - Wrong here is that Point redefined equals without also redefining hashCode.- The problem was that the last implementation of Point violated the contract on hashCode as defined for class Any: If two objects are equal according to the equals method, then calling the hashCode method on each of the two objects must produce the same integer result.- In fact, it’s well known in Java that hashCode and equals should always be redefined together.- Furthermore, hashCode may only depend on fields that equals depends on.- For the Point class, the following would be a suitable definition of hashCode: override def hashCode = 41 * (41 + x) + y // Redefine hashCode- This is just one of many possible implementations of hashCode.- Adding hashCode fixes the problems of equality when defining classes like Point.
  14. 14. class Point(val x: Int, val y: Int) { override def hashCode = 41 * (41 + x) + y // Redefine hashCode override def equals(other: Any) = other match { // Redefine equals() case that: Point => this.x == that.x && this.y == that.y case _ => false }}scala> val p1, p2 = new Point(1, 2)p1: Point = Point@6bcp2: Point = Point@6bcscala> coll contains p2 res2: Boolean = true
  15. 15. Pitfall #3: Defining equals in terms of mutable fields: - Consider the following slight variation of class Point: class Point2(var x: Int, var y: Int) { override def hashCode = 41 * (41 + x) + y // Redefine hashCode override def equals(other: Any) = other match { // Redefine equals() case that: Point2 => this.x == that.x && this.y == that.y case _ => false } } scala> val p = new Point(1, 2) p: Point = Point@2b scala> val coll = HashSet(p) coll: scala.collection.mutable.Set[Point] = Set(Point@2b) scala> coll contains p res5: Boolean = true scala> p.x += 1 // Changing p.x value scala> coll contains p res7: Boolean = false scala> coll.elements contains p res7: Boolean = true
  16. 16. coll.elements contains p true p.x += 1 After change value it (x:mutable) assign to wrong hash bucket p.x=1 p.x=2 coll contains p coll false Hash Bucket Hash Bucket Pitfall #3: Defining equals in terms of mutable fields- In a manner of speaking, the point p “dropped out of sight” in the set coll even though it still belonged to its elements.- If you need a comparison that takes the current state of an object into account, you should usually name it something else, not equals.
  17. 17. Pitfall #4: Failing to define equals as an equivalence relation: - In scala, Any class specifies that equals must implement an equivalence relation on non-null objects: • It is reflexive: for any non-null value x , the expression x.equals(x) should return true. • It is symmetric: for any non-null values x and y, x.equals(y) should return true if and only if y.equals(x) returns true. • It is transitive: for any non-null values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true. • It is consistent: for any non-null values x and y, multiple invocations of x.equals(y) should consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified. • For any non-null value x, x.equals(null) should return false. - The definition of equals developed so far for class Point satisfies the contract for equals. - However, things become more complicated once subclasses are considered.
  18. 18. Symmetric Problem: class Point(val x: Int, val y: Int) { override def hashCode = 41 * (41 + x) + y // Redefine hashCode override def equals(other: Any) = other match { // Redefine equals() case that: Point => this.x == that.x && this.y == that.y case _ => false } } object Color extends Enumeration { val Red, Orange, Yellow, Green, Blue, Indigo, Violet = Value } class ColoredPoint(x: Int, y: Int, val color: Color.Value) extends Point(x, y) { // Problem: equals not symmetric override def equals(other: Any) = other match { case that: ColoredPoint =>this.color == that.color && super.equals(that) case _ => false } } scala> val p = new Point(1, 2) p: Point = Point@2b scala> val cp = new ColoredPoint(1, 2, Color.Red) cp: ColoredPoint =ColoredPoint@2b scala> p equals cp res:0 Boolean : true scala> cp equals p res:0 Boolean : false
  19. 19. Solution of Symmetric Problem but violated Transitivity contract: class Point(val x: Int, val y: Int) { override def hashCode = 41 * (41 + x) + y // Redefine hashCode override def equals(other: Any) = other match { // Redefine equals() case that: Point => this.x == that.x && this.y == that.y case _ => false } } object Color extends Enumeration { val Red, Orange, Yellow, Green, Blue, Indigo, Violet = Value } class ColoredPoint(x: Int, y: Int, val color: Color.Value) extends Point(x, y) { // Problem: equals not transitive override def equals(other: Any) = other match { case that: ColoredPoint =>this.color == that.color && super.equals(that) case that:Point=>that equals this //new case to make symmetric case _ => false } } scala> p equals cp res:0 Boolean : true scala> cp equals p res:0 Boolean : true
  20. 20. - Here’s a sequence of statements that demonstrates transitive. Define a point and two colored points of different colors, all at the same position: scala> var redp=new ColoredPoint(1,2,Color.Red) redp: ColoredPoint = ColoredPoint@6bc scala> var bluep=new ColoredPoint(1,2,Color.Blue) bluep: ColoredPoint = ColoredPoint@6bc scala> var p=new Point(1,2) p: Point = Point@6bc scala> redp == p res1: Boolean = true scala> p == bluep res1: Boolean = true scala> redp == bluep res1: Boolean = false- Hence, the transitivity clause of equals’s contract is violated.- One way to make equals stricter is to always treat objects of different classes as different.- That could be achieved by modifying the equals methods in classes Point and ColoredPoint.- In class Point, you could add an extra comparison that checks whether the run-time class of the other Point is exactly the same as this Point’s class, as follows:
  21. 21. Solution of Transitivity Problem:class Point(val x: Int, val y: Int) { override def hashCode = 41 * (41 + x) + y // Redefine hashCode override def equals(that: Any) = other match { // Redefine equals() case that: Point => this.x == that.x && this.y == that.y && this.getClass == that.getClass case _ => false }} object Color extends Enumeration { val Red, Orange, Yellow, Green, Blue, Indigo, Violet = Value }class ColoredPoint(x: Int, y: Int, val color: Color.Value) extends Point(x, y) { override def equals(that: Any) = other match { case that: ColoredPoint =>(this.color == that.color)&& super.equals(that) case _ => false }}scala> redp == p res:0 Boolean : falsescala> p == bluep res:0 Boolean : falsescala> redp == bluep res:0 Boolean : false
  22. 22. - Here, an instance of class Point is considered to be equal to some other instance of the same class only if the objects have the same coordinates and they have the same run-time class.- Meaning .getClass on either object returns the same value.- The new definitions satisfy symmetry and transitivity because now every comparison between objects of different classes yields false.- So a colored point can never be equal to a point.

×