Equals (Java)
How and why to write custom equals methods in Java
Why override standard equals?
By default, every Java object has an equals(Object o)
method which is inherited from the Object
class. The implementation of this equals
method compares objects using their memory locations, meaning that two objects are only considered equal if they actually point to the exact same memory location and are thus really one and the same object.
If you want to define equality in such a way that two objects can be considered equal even if they are not really the exact same object in the exact same memory location, you will need a custom equals
implementation.
equals
method
The requirements for a good - Reflexivity: every object is equal to itself
- Symmetry: if a is equal to b, then b is also equal to a
- Transitivity: if a is equal to b and b is equal to c, then a is also equal to c
- Consistency: if a is equal to b right now, then a is always equal to b as long as none of their state that is used in the
equals
method has been modified - Non-nullity: an actual object is never equal to
null
Example class
Naïve implementations
equals
method
Not properly overriding the Problem: equals(Point)
does not properly override equals(Object)
because the signature doesn't match.
- In the first assertion, we are calling a method with signature
equals(Object)
on an object with compile-time typePoint
. AsPoint
does not implement a method with that signature, the best match is theequals(Object)
method inherited fromObject
. - In the second assertion, we are calling a method with signature
equals(Point)
on an object with compile-time typeObject
. AsObject
does not have anequals(Point)
method, the best match at compile time is itsequals(Object)
method. And, becausePoint
(the run-time type ofpointObject
) does not override that method, the actual implementation that gets called is still the one defined inObject
.
See also Overloading, overriding and method hiding
hashCode
Forgetting about Problem: HashSet
uses hashCode
, and default implementation is likely to return different hash codes for different objects (not same memory location)
Mutable variables
Problem: changing x
also changes the hash code, which means that the hash bucket where the set now looks for the point is different from the hash bucket where the point ended up based on its initial hash code.
Simple decent implementation
The equals
and hashCode
methods are pretty much what Eclipse generates by default
Dealing with subclasses
Allowing subclasses to be equal to superclasses
Problem with equals
method using getClass
:
Reason: this.getClass()
returns different class for objects of different classes!
Solution: replace
with
Most IDEs have option to do this when generating equals
.
equals
Subclasses including additional state in What if some subclasses of Point
have additional info to consider when determining if objects are equal?
Example:
What if we want to include the color in the equals
method so that a ColorPoint(1, 1, Color.RED)
is not equal to a ColorPoint(1, 1, Color.BLUE)
?
General remark
If we want this, we have to accept that a ColorPoint
will never be equal to any Point
. The reason for this is transitivity (see above). If we want to say that ColorPoint(1, 1, Color.RED)
and ColorPoint(1, 1, Color.BLUE)
are both equal to Point(1, 1)
, then transitivity would imply that they are also equal to each other. That is exactly what we didn't want here.
This could be seen as a violation of the Liskov substitution principle
The hard, but potentially more correct way
Benefits:
- passes all of the previous tests
- still allows subclasses of
Point
that do not include additional state to be equal to aPoint
The easy way
Simply use getClass()
again!
Drawback: objects can only be equal if they are or exactly the same class
In practice
Recommended approach:
- Let your IDE generate your
equals
(andhashCode
) methods for you, usinginstanceof
instead ofgetClass()
. - Either make your class
final
or make yourequals
andhashCode
methodsfinal
.
Note that the two options outlined in step 2 have different effects:
- Making your class
final
prevents any issues with subclasses by simply not allowing subclasses for your class. - Making your
equals
andhashCode
methodsfinal
prevents subclasses from overriding yourequals
andhashCode
methods and including additional state in them.
In cases where this is not sufficient (you want subclasses to include additional state in their equals
method), consider using the solution involving the canEqual
method or the simpler solution using getClass
if you’re ok with subclass instances never being equal to superclass instances.
equals
methods
Testing Better alternative to hand-written equals
tests: the EqualsVerifier library by Jan Ouwens.
Uses reflection to inspect class and test its equals
and hashCode
methods with 100% coverage.
Resources
- EqualsVerifier
- How to Write an Equality Method in Java
- Core Java SE 9 for the Impatient (book by Cay S. Horstmann)
- Overloading in the Java Language Specification