object equality in scala

Download Object Equality in Scala

If you can't read please download the document

Upload: knoldus-software-llp

Post on 16-Apr-2017

5.562 views

Category:

Technology


0 download

TRANSCRIPT

Object Equality In Scala

Ruchi AgarwalSoftware ConsultantKnoldus

Object Equality In Java & Scala:

- Two equality comparisons:1. == Operator2. 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

- 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 type's 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 Scala

scala> def isEqual(x : Int , y : Int) = x == yisEqual: (x: Int, y: Int )Boolean

scala> isEqual( 421 , 421)res8: Boolean = true

scala> def isEqual(x : Any , y : Any) = x == yisEqual: (x: Any , y: Any)Boolean

scala> isEqual( 421 , 421)res8: Boolean = true

String Comparisons:

- Scala, never fall into Java's well-known trap concerning string comparisons.

//This is Scala

scala> val x=abcd.substring(2)x: java.lang.String = cd

scala> val y=abcd.substring(2)y: java.lang.String = cd

scala> 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

Reference Equality:

- For refernce equality, Scala's 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 = abc

scala> val x=new String("abc")x: java.lang.String = abc

scala> x == yres13: Boolean = true// Value equality

scala> x eq yres14: Boolean = false // Reference Equality

- There's also the negation of eq , which is called ne.

scala> x ne yres14: Boolean = true

- 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 class

final def == (that : Any) : Boolean = if ( null eq this) { null eq that }else { this equals that }

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

class 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@139fb49

scala> 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

//Trouble

scala> val p2a: Any = p2// Alias of p2p2a: Any = Point@139fb49

// p2a calling equals() of Any classscala> p1 equals p2ares:0 Boolean : false

Pitfall #1: Defining equals with wrong signature

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 p2p2a: Any = Point@139fb49

scala> p1 equals p2ares:0 Boolean : true

- 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 = // Dont 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.

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@1bef480p2: Point = Point@1a6068c

//storing p1 to collectionscala> val coll = HashSet (p1)coll: scala.collection.mutable.Set[Point] =Set(Point@62d74e)

scala> coll contains p2res2: Boolean = false

- 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, well get one which is not contained in the set.

hashSet(p1) contains p2

val p1

hashSetHash Bucket

Hash Bucket

Pitfall #2: Changing equals without also changing hashCode

val p2

val p2

false

true

- 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, its 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:

- This is just one of many possible implementations of hashCode.

- Adding hashCode fixes the problems of equality when defining classes like Point.

override def hashCode = 41 * (41 + x) + y// Redefine hashCode

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.ycase _ => false

}

}

scala> val p1, p2 = new Point(1, 2)p1: Point = Point@6bcp2: Point = Point@6bc

scala> coll contains p2res2: Boolean = true

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.ycase _ => 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 pres5: Boolean = true

scala> p.x += 1// Changing p.x value

scala> coll contains pres7: Boolean = false

scala> coll.elements contains pres7: Boolean = true

p.x=1

collHash Bucket

p.x += 1(x:mutable)

p.x=2

Hash Bucket

Pitfall #3: Defining equals in terms of mutable fields

After change value it assign to wrong hash bucket

coll.elements contains ptrue

coll contains pfalse

- 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.

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.

class Point(val x: Int, val y: Int) {

override def hashCode = 41 * (41 + x) + y// Redefine hashCodeoverride def equals(other: Any) = other match {// Redefine equals()case that: Point => this.x == that.x && this.y == that.ycase _ => 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@2bscala> val cp = new ColoredPoint(1, 2, Color.Red)cp: ColoredPoint =ColoredPoint@2bscala> p equals cp res:0 Boolean : truescala> cp equals p res:0 Boolean : false

Symmetric Problem:

class Point(val x: Int, val y: Int) {

override def hashCode = 41 * (41 + x) + y// Redefine hashCodeoverride def equals(other: Any) = other match {// Redefine equals()case that: Point => this.x == that.x && this.y == that.ycase _ => 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 symmetriccase _ => false}}scala> p equals cp res:0 Boolean : truescala> cp equals p res:0 Boolean : true

Solution of Symmetric Problem but violated Transitivity contract:

- Heres a sequence of statements that demonstrates transitive. Define a point and two colored points of different colors, all at the same position:

- Hence, the transitivity clause of equalss 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 Points class, as follows:

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 == pres1: Boolean = truescala> p == bluepres1: Boolean = truescala> redp == bluepres1: Boolean = false

class Point(val x: Int, val y: Int) {

override def hashCode = 41 * (41 + x) + y// Redefine hashCodeoverride def equals(that: Any) = other match {// Redefine equals()case that: Point => this.x == that.x && this.y == that.y && this.getClass == that.getClasscase _ => 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

Solution of Transitivity Problem:

- 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.

ThankYou