Maximum of us know how to override equals() and what is the use of it. It’s easy, but the most important part where we fail is to understand when to override. We tend to confuse whether to override an equals() or not. It’s better to avoid equals() whenever possible. But how come we would know that ‘whenever’. Let me list out the ‘whenever’ conditions where we should avoid overriding equals().
·
When you know that each instance of the class is
unique. For e.g. Thread, each instance
has to be unique as it represents active entity. So equals() implemented in Object class is
perfect for it.
·
You are not sure whether the objects should be logical
equal. For e.g. java.util.Random could have overridden equals(),
but the designers must have not be sure that the client(the end programmer)
would need to know that the produced random numbers from two different
instances of Random are same or not.
·
Superclass has already overridden
equals() and it fits the requirement with the subclass. For e.g. Set overrides equals() and the same
implementation is used for its subclasses like HashSet.
·
It is a private class or a
package-private class and it is sure that the equals() will never be
called. Though we can override the equals()
here incase accidently equals get called.
In that case we can just throw a AssertionError() in the body of equals().
Hopefully by now it is clear as
to when not to override equals().Let us discuss on how to override equals(), rather we would discuss what are the points which should be considered while overriding equals().
·
Reflexive:
Mathematically it means, for any non-null reference value x, x.equals(x) must
return true. It’s hard to violate this
rule.
·
Symmetric:
This means for any non-null reference values x and y, x.equals(y) must
return true if
and only if y.equals(x) returns true.
Let us see
how an equals() overriding can violate this rule.
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
if (s == null) {
throw new NullPointerException();
}
this.s = s;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CaseInsensitiveString) {
return s.equalsIgnoreCase(((CaseInsensitiveString)
obj).s);
}
if (obj instanceof String) {
return s.equalsIgnoreCase((String) obj);
}
return true;
}
}
Now if we
try to run the below line of code,
CaseInsensitiveString
c = new CaseInsensitiveString("Test");
String s = "test";
System.out.println("c.equals(s):" + c.equals(s));
System.out.println("s.equals(c):" + s.equals(c));
The result
will be:
c.equals(s):true
s.equals(c):false
This clearly
shows how it violates rule of symmetry.
This is because String class equals() is not aware of case-insensitive
strings. In order to make this symmetric
we need to do something like this:
@Override
public boolean
equals(Object obj) {
return obj instanceof CaseInsensitiveString && ((CaseInsensitiveString) obj).s.equalsIgnoreCase(s);
}
·
Transitive: For any non-null reference values x, y and z if
x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) must return true.
Let us
consider a case of a superclass and a subclass.
Superclass has some properties and overrides equals. Subclass adds some
more properties and try to override equals()
public class Point {
private final int x;
private final int y;
public Point(int
x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Point)) {
return false;
}
Point p = (Point) obj;
return p.x == x && p.y == y;
}
}
public class ColourPoint extends Point {
private final int colour;
public ColourPoint(int
x, int y, int colour) {
super(x, y);
this.colour = colour;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ColourPoint)) {
return false;
}
return super.equals(obj) &&
((ColourPoint) obj).colour == colour;
}
}
When we do,
Point
p = new Point(2, 3);
ColourPoint cp = new
ColourPoint(2, 3,
8);
System.out.println("p.equals(cp):" + p.equals(cp));
System.out.println("cp.equals(p):" + cp.equals(p));
We will get,
p.equals(cp):true
cp.equals(p):false
This
pathetically fails for even symmetry. The
reason is clear. ColourPoint’s equals() is
trying to call Point’s equals() which is returning true here, thus violating
the rule. Let us see another approach,
@Override
public boolean
equals(Object obj) {
if (!(obj instanceof Point)) {
return false;
}
if (!(obj instanceof ColourPoint)) {
return obj.equals(this);
}
return super.equals(obj)
&& ((ColourPoint) obj).colour == colour;
}
This
overriding in ColourPoint will solve symmetry but not transitivity. How??
This is how:
Point
p1 = new Point(2, 3);
ColourPoint cp1 = new ColourPoint(2, 3, 8);
ColourPoint cp2 = new ColourPoint(2, 3, 9);
System.out.println("cp1.equals(p1):" + cp1.equals(p1));
System.out.println("p1.equals(cp2):" + p1.equals(cp2));
System.out.println("cp2.equals(cp1):" + cp2.equals(cp1));
The result would
be,
cp1.equals(p1):true
p1.equals(cp2):true
cp2.equals(cp1):false
Sadly, there
is no way to extend an instantiable class and add a value component while
preserving the equals contract, unless you are willing to forgo the benfits of
object oriented-programming.
But there
can be a workaround. Instead of having
ColourPoint extend Point keep a private Point field in ColourPoint.
public class ColourPoint {
private final Integer colour;
private final Point point;
public Point asPoint() {
return point;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ColourPoint)) {
return false;
}
ColourPoint cp = (ColourPoint) obj;
return cp.point.equals(point) && cp.colour.equals(colour);
}
}
Note: java.sql.Timestamp extends java.util.Date and
adds a nanoseconds field. The equals() implementation for Timestamp
does violates symmetry. It has
disclaimer stating this.
·
Consistent:
For any non-null reference values x and y, multiple
invocations of x.equals(y) consistently
return true or
consistently return false, provided no information is changed
on objects. Best way to keep up to this
is to avoid writing equals() which depends on unreliable resources.
·
For any non-null reference x.x equals(null) must
return false.
We can do
something like this,
@Override
public boolean equals(Object obj) {
if(obj == null) {
return false;
}
...
}
Or this,
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Point)) {
return false;
}
.......
}
While executing this, it will cast the object and can throw ClassCastException, but
it won’t. Because instanceof operator
will return false if it throws exception.
Now some
important points to be remembered while implementing equals().
1.
Use == operator to check if the argument is a
reference to this object. This is for
performance optimization.
2.
Use the instanceof operator
to check if the argument has the correct type.
3.
Cast the argument
to the correct type. No need to worry about ClassCastException, because it will be preceeded by instanceof so
it has to be a success.
4.
For each “significant”
field in the class, check if that field of the argument matches the corresponding
field of this object.
(field
== null ? obj.feild == null : field.equals(o.field))
5.
Follow the
rules of symmetric, transitivity and consistency.
Some Points
to remember:
·
Always
override hashCode when you override equals()
·
Don’t
substitute another type for Object in the equals declaration.
public boolean equals(MyClass obj)
·
Don’t try to
exaggerate. That means only checking fields
equality will do the job; don’t try hard on other things which can lead you
into trouble.
**Note: You can use org.apache.commons.lang.builder.EqualsBuilder for writing a good equals(). For more information you can see http://commons.apache.org/lang/api-2.5/org/apache/commons/lang/builder/EqualsBuilder.html
{Courtesy: Effective Java - Joshua Bloch}
**Note: You can use org.apache.commons.lang.builder.EqualsBuilder for writing a good equals(). For more information you can see http://commons.apache.org/lang/api-2.5/org/apache/commons/lang/builder/EqualsBuilder.html
{Courtesy: Effective Java - Joshua Bloch}
No comments:
Post a Comment