| Effective implementation of equals method | 
|---|
|  | 
|  | 
|  | 
| Abstract"Effective Java Programming 
      Language Guide" by Joshua Bloch presents fifty seven items for a Java 
      developer to keep in mind while developing quality code. There are a 
      number of very interesting suggestions and gochas raised in it, including 
      issues with cloning, problems with automatic garbage collection, benefits 
      and usage of immutable objects, when to avoid inheritance, to mention a 
      few. This is a very good book to read and we strongly recommend it to any 
      serious Java developer. In this article, we bring to you Johua's 
      discussions about the effective implementation of the equals method (Item 
      #7). While we agree with the problems he has raised, we don't agree with 
      his final conclusion. We go further to present how the concerns could be 
      easily addressed. | 
| The equals methodObjects may be compared by value or for 
      their identity. To compare the identity, you simply use the ==operator. If you want to compare objects by value, you check if two 
      objects are equal based on their current state or values of their attributes/data. 
      The Object base class provides the equal method, which by default returns 
      true if the argument reference is identical to the reference on which 
      equals is invoked. Traditionally, one would override the equals method to 
      provide meaningful comparison of two objects. | 
| Rules to follow in Overriding equalsThe Java language 
    specifications 1, 2, 3 describes the equals method as a method that indicates if an 
      object is equals to another and it implements the equalance relation. The 
      equals method should be reflexive, symmetric, transitive, consistent and 
      should return a false if the reference argument is null. 
 | 
| 
     Problem with maintaining Symmetry and Transitivity Here we discuss the issues with symmetry and transitivity with examples presented in Effective Java1. Let us consider a class Point as shown below: 
public class Point
{
	private final int x;
	private final int y;
	public Point(int px, int py)
	{
		x = px;
		y = py;
	}
	public boolean equals(Object o)
	{
		if (!(o instanceof Point))
			return false;
		Point p = (Point) o;
		return p.x == x && p.y == y;
	}
}
	Now consider a class ColorPoint that extends Point as shown below:
public class ColorPoint extends Point
{
	private Color color;
	
	public ColorPoint(int px, int py, Color clr)
	{
		super(px, py);
		color = clr;
	}
	
	public boolean equals(Object o)
	{
		if (!(o instanceof ColorPoint))
			return false;
		ColorPoint cp = (ColorPoint) o;
		//return super.equals(o) && cp.color == color;
		// The above commented line is from1. We have modified it
		// as follows:
		return super.equals(o) && color.equals(cp.color);
	}
}
	Now, if we try the following scenario: Point p = new Point(1, 2); ColorPoint cp = new ColorPoint(1, 2, Color.red);p.equals(cp) returns a true, however, cp.equals(p) returns a false. This fails the symmetry. One way to fix this as suggested in1 is: 	// ColorPoint's equals method
	public boolean equals(Object o)
	{
		if (!(o instanceof Point))
			return false;
		
		// If o is a normal Point, do a color-blind comparison
		if (!(o instanceof ColorPoint))
			return o.equals(this);
		
		// o is a ColorPoint; do a full compoarison
		ColorPoint cp = (ColorPoint) o;
		//return super.equals(o) && cp.color == color;
		// The above commented line is from1. We have modified it
		// as follows:
		return super.equals(o) && color.equals(cp.color);
	}
	While this solves the symmetry problem, it fails trasitivity. Consider:ColorPoint p1 = new ColorPoint(1, 2, Color.red); Point p2 = new Point(1, 2); ColorPoint p3 = new ColorPoint(1, 2, Color.blue);Now, while p1.equals(p2) returns a true and p2.equals(p3) returns a true, p1.equals(p3) returns a false. The first two performed a color-blind comparison, while the third considered the color in the comparison. | 
| Concerns and recommendations from Effective JavaThe following are the recommendations from "Effective Java."1 "So what's the solution? It turns out that this is a fundamental problem of equivalence relations in object-oriented languages. There is simply no way to extend an instantiable class and add an aspect while preserving the equals contract. There is, however, a fine workaround. Follow the advice of Item 14, 'Favor composition over inheritance.' Instead of having ColorPoint extend Point, give ColorPoint a private Point field and a public view method (Item 4) that returns the point at the same position as this color point:" | 
| Easy fix!While
  I separately agree with the argument of using composition over inheritance, in
  this particular problem, there is actually an easier work around. In 
  the equals method, we want to compare further only if the objects are of the 
  same type. If the objects are of different types, we can decide to return
  a false. Now, how do we decide if the objects are of different types? 
  In the original implementation of ColorPoint's equals method we tried this, and
  failed. It failed symmetry. Let's try again. Let's revisit the equals of the 
  ColorPoint and Point: 
// Point's equals method
public boolean equals(Object o)
{
	if (!(o instanceof Point))
		return false;
	Point p = (Point) o;
	return p.x == x && p.y == y;
}
// ColorPoint's equals method
public boolean equals(Object o)
{
	if (!(o instanceof ColorPoint))
		return false;
	ColorPoint cp = (ColorPoint) o;
	return super.equals(o) && color.equals(cp.color);
}
	The ColorPoint checks to see if the given object is also a ColorPoint.
If not, it returns false. This is good. However, to the Point's equals
method, if we send a ColorPoint as an argument, the instanceof will
identify this object as an instance of the Point class and so, it will
perform a color-blind comparison. This is why symmetry was failing. This
problem, however, can be eliminated as follows:
//Point's equals method
public boolean equals(Object o)
{
	if (!(o.getClass() == getClass()))
		return false;
	Point p = (Point) o;
	return p.x == x && p.y == y;	
}
//ColorPoints' equals method
public boolean equals(Object o)
{
	if (!(o.getClass() == getClass()))
		return false;
	ColorPoint cp = (ColorPoint) o;
	return super.equals(o) && color.equals(cp.color);
}
Each class can check to see if the object being pass in as argument 
      is exactly   
                
  the same type as the one on which equals is called. This way,
 if p.equals(cp) is called, where p is a reference to a Point object and
 cp to ColorPoint, the equals will return a false. Similarly, cp.equals(p)
 will  return a false. However, cp1.equals(cp2), where both references cp1
 and cp2 refer to object of ColorPoint, will return a true if the point
 values are equal and the colors are the same as well. | 
| ConclusionWhile polymorphism and substituitability (and overriding) are concepts that provide
  great extensibility in a system, we have to be very careful in implementing
  these concepts. It requires quite a bit of insight and analysis to get it
  done correct. The issues presented in "Effective Java"1 are not only interesting,
  but also very important. If you have not read through it, I hope
  you will soon. We will present a few other issues from that book in future
  issues and also discuss our opinion on what we may agree and some that
  we may not! | 
| References 
 |