Know how to implement hashcode and equals
Summary
Implementing hashCode
and equals
is not straight-forward. Do not implement them unless it is necessary to do so. If you do implement them make sure you know what you are doing.
Details
It is well known that if you override equals you must also override the hashCode
method (see Effective Java item 9).
If logically equal objects do not have the same hashCode
they will behave in a surprising manner if placed in a hash based collection such as HashMap
.
By surprising we mean make your program behave incorrectly in a fashion that it very difficult to debug.
Unfortunately implementing equals is surprisingly hard to do correctly. Effective Java item 8 spends about 12 pages discussing the topic.
The contract for equals is handily stated in the Javadoc of java.lang.Object
. We will not repeat it here or repeat the discussion of what it means that can be found in Effective Java and large swathes of the internet, instead we will look at strategies for implementing it.
Which ever strategy you adopt it is important that you first write tests for your implementation.
It is easy for an equals method to cause hard to diagnose bugs if the code changes (e.g if a fields are added or their type changes). Writing tests for equals methods used to be a painful and time-consuming procedure, but libraries now exist that make it trivial to specify the common cases (see Testing FAQs).
Don't
This is the simplest strategy and the one you should adopt by default in the interests of keeping your codebase small.
Most classes do not need an equals method. Unless your class represents some sort of value it makes little sense to compare it with another so stick with the inherited implementation from Object.
An irritating grey area are value classes where the production code never has a requirement to compare equality but the test code does. The dilemma here is whether to implement the methods purely for the benefit of the tests or to complicate the test code with custom equality checks.
There is of course no right answer here, but we would suggest first trying the compare-it-in-the test approach before falling back to providing a custom equals method.
The custom equality checks can be cleanly shared by implementing a custom assertion using a library such as AssertJ or Hamcrest.
Effective Java tentatively suggests having your class throw an error if equals is unexpectedly called
@Override public boolean equals(Object o) {
throw new AssertionError(); // Method is never called
}
This seems like a good idea, but unfortunately will confuse most static analysis tools. On balance it probably creates more problems than it solves.
Auto generate with an IDE
Most IDEs provide some method of auto-generating hashCode
and equals
methods. This is an easily accessible method of generating hashCode
and equals
, but the resulting methods are (depending on the IDE and its settings) often ugly and complex such as the ones generated by Eclipse shown below.
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((field1 == null) ? 0 : field1.hashCode());
result = prime * result + ((field2 == null) ? 0 : field2.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyClass other = (MyClass) obj;
if (field1 == null) {
if (other.field1 != null)
return false;
} else if (!field1.equals(other.field1))
return false;
if (field2 == null) {
if (other.field2 != null)
return false;
} else if (!field2.equals(other.field2))
return false;
return true;
}
Unless your IDE can be configured to produce clean methods (as discussed below) we do not generally recommend this approach as it is easy for bugs to be introduced into this code by hand editing over time.
Hand roll clean methods
Java 7 introduced the java.util.Objects
class that makes implementing hashCode
trivial. Guava provides the similar com.google.common.base.Objects
class which may be used with earlier versions of Java.
@Override
public int hashCode() {
return Objects.hash(field1, field2);
}
The Objects
class also simplifies implementing equals a little by pushing most null checks into the Objects.equals
method.
@Override
public boolean equals(Object obj) {
if (this == obj) // <- performance optimisation
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass()) // <- see note on inheritance
return false;
MyClass other = (MyClass) obj;
return Objects.equals(field1, other.field1) &&
Objects.equals(field2, other.field2);
}
The first if statement is not logically required and could be safely omitted, it may however provide performance benefits.
Usually we would recommend that such micro-optimisations are not included unless they are been proven to provide a benefit, but in the case of equals methods we suggest that the optimisation is left in place. It is likely to justify itself in at least some of your classes and there is value in having all methods follow an identical template.
The example above uses getClass
to check that objects are of the same type. An alternative is to use instanceof
as follows
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof MyClass)) // <- compare with instanceof
return false;
MyClass other = (MyClass) obj;
return Objects.equals(field1, other.field1) &&
Objects.equals(field2, other.field2);
}
This results in a behavioural difference - comparing instances of MyClass
with its subclasses will return true with instanceof
but false with getClass
.
In Effective Java Josh Bloch argues in favour of instanceof
as the getClass
implementation violates a strict interpretation of the Liskov substitution principle.
However if instanceof
is used it is easy for the symmetric property of the equals contract to be violated if a subclass overrides equals. i.e
MyClass a = new MyClass();
ExtendsMyClassWithCustomEqual b = new ExtendsMyClassWithCustomEqual();
a.equals(b) // true
b.equals(a) // false, a violation of symmetry
If you find yourself in a situation where you need to consider the nuances of whether subclasses are equal to their parents we strongly suggest you reconsider your design.
Having to think about maintaining the equals contract in a class hierarchy is painful and you shouldn't need to put yourself or your team through this for normal server side coding tasks.
In the majority of cases if you think it makes sense for your class to implement hashCode
and equals
we strongly suggest you make your class final so hierarchies do not need to be considered.
If you believe you have a case where it makes sense for subclasses to be treated as equivalent to their parent use instanceof
, but ensure that the parent equals method is made final.
Avoid relationships that are more complex than this.
Commons EqualsBuilder and HashCodeBuilder
The apache commons hashcode and equals builders were once a popular way of generating these methods. We do not recommend their use in new code as most of what they achieved is now provided by java.util.Objects
without bringing in a 3rd party library, or by the Guava equivalent.
These classes do provide the option of a single line reflection based implementation.
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
The brevity of these implementations is attractive, but their performance is measurably poor compared to all the implementations discussed so far. If you are confident that you will pick up real performance bottlenecks with testing and profiling then using these as initial placeholder implementations may be a reasonable approach, but in general we suggest you avoid them.
Lombok
Lombok allows you to annotate your class so that hashCode
and equals
methods are generated at build time. To be able to work with code using Lombok you will also require support for the annotations in your IDE.
Although auto-generating boilerplate methods is on the face of it a very sensible approach, most teams we have spoken to that have adopted Lombok and similar technologies have eventually removed them due to the friction caused by the tooling and the confusion caused by the not-quite-java magic.