According to the Javadocs, the equals
method must be:
- reflexive: for any non-null reference value
x
, x.equals(x)
should return true
.
- symmetric: for any non-null reference values
x
and y
, x.equals(y)
should return true
if and only if y.equals(x)
returns true
.
- 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)
should return true
.
- 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 used in equals
comparisons on the objects is modified.
- return
false
for x.equals(null)
, for any non-null reference value x
.
In other words, the following tests must pass:
@Test
public void testEquals() {
// reflexive
assertTrue(x.equals(x));
// symmetric
assertTrue(x.equals(y) == y.equals(x));
// transitive
if (x.equals(y) && y.equals(z)) {
assertTrue(x.equals(z));
}
// consistent
assertTrue(x.equals(y) == x.equals(y));
// null check
assertFalse(x.equals(null));
}
Implementing Equals(): Approach 1
The steps are:
- Check reference equality (good optimisation step)
- Check correct class type using
instanceof
- Cast to correct type
- Compare objects
@Override
public boolean equals(Object obj) {
// check reference equality
if (this == obj) {
return true;
}
// check correct arg type
if (!(obj instanceof Rectangle)) {
return false;
}
// cast the object to the correct type
Rectangle other = (Rectangle) obj;
// compare fields
return other.getLength() == length &&
other.getWidth() == width;
}
But what happens if you extend this class by adding another field? For example, a Rectangle
has a length and width. But what if we create a Cuboid
that extends Rectangle
with an additional depth attribute?
The problem occurs when you mix objects of Rectangle and Cuboid because Rectangle instanceof Cuboid
returns false
, whereas Cuboid instanceof Rectangle
returns true
. This breaks the symmetric rule because rectangle.equals(cuboid)
is true
, but cuboid.equals(rectangle)
is false
. In order to preserve symmetry, we can change our equals
method as follows:
Implementing Equals(): Approach 2
- Check if null
- Check correct class type using
getClass
- Cast to correct type
- Compare objects
@Override
public boolean equals(Object obj) {
// check null
if (obj == null) {
return false;
}
//check correct type
if(getClass() != obj.getClass()) {
return false;
}
// cast the object to the correct type
Rectangle other = (Rectangle) obj;
// compare fields
return other.getLength() == length &&
other.getWidth() == width;
}
The getClass
method will always return false
if the parameter is not the exact same type as the object class.
In most cases, you should use Approach 2 so that you obey the equals
contract, unless you want to compare subclasses with their base types.