Both hashCode and equals(Object) should be overridden for entity objects that have a database identity and value objects that are used to pass parameters and return result, especially if they are used in a Set or a Map.
An often overlooked benefit of overriding these two methods for value objects is that it makes assertion and verification of invocation on mock objects much easier and more resilient to changes.
As for stateless objects, such as web actions, service objects, entity managers, repositories and data access objects, there is little to gain by overriding these methods.
With regards to toString, it would make our life debugging or reading log file a lot easier if it is overridden even though business may not mandate this.
In "Effective Java", Joshua Bloch provides the following guidance on when it is appropriate to override these methods:
- Override equals(Object) unless:
- Each instance of the class is inherently unique.
- You don't care whether the class provides a "logical equality" test.
- A superclass has already overridden equals, and the superclass behaviour is appropriate for this class.
- The class is private or package-private, and you are certain that its equals method will never be invoked.
- (Item 9) Always override hashCode when you override equals
- (Item 10) Always override toString
But, why are we not overriding these methods as often as we should? I can think of the following reasons:
- When we write a new class, our focus is on the implementation of the core responsibility of the class. Overriding these methods are often a result of after thoughts.
- We can "cheat" in unit testing by substituting "logical equality" test with a "uniqueness" test, especially if the value object is immutable so that the class under test does not need to defensively copy an incoming value object to preserve invariants.
- As there is a fair bit of code in overriding equals(Object), it requires unit testing the correctness of the overriding, which can be several times longer than the overriding method itself, depending on how thorough your tests are.
- These methods are considered "affordable debt" in order to meet a project deadline.
Is there a way to override these methods with least effort?
Yes. We'll go through several ways of overriding these methods to find the simplest way.
DIY
When hand-crafting equals method, make sure you follow the high-quality equals method recipe from "Effective Java":
- Use the == operator to check if the argument is a reference to this object.
- Use the instanceof operator to check if the argument has the correct type.
- Cast the argument to the correct type.
- For each "significant" field in the class, check if that field of the argument matches the corresponding field of this object.
- Avoid the possibility of a NullPointerException if some instance fields are nullable.
- When you are finished writing your equals method, ask yourself three questions: Is it symmetric? Is it transitive? Is it consistent?
Some additional suggestions:
- If the superclass also overrides equals method, invoke super.equals(obj) as well, provided that the superclass's overriding method does not use a getClass test in place of the instanceof test.
- Unit test symmetry, transitivity and consistency.
Pros
- No third-party dependencies
Cons
- Humans are error-prone, so it definitely requires unit-testing
- Can be time-consuming
- Requires "maintenance" when adding new instance fields
- equals(Object and hashCode() can get out-of-sync when introducing new instance fields
Code generation by IDE
In IDEA, select "Code" -> "Generate..." and select "equals() and hashCode()".
In Eclipse, select "Source" -> "Generate equals() and hashCode()...".
Both IDEs let you choose whether to use instanceof test or not, and which instance fields are used in these methods.
Pros
- No third-party dependencies
Cons
- Requires re-generation when adding new instance fields
- Unit testing may be needed if the generated code is significantly "enhanced by hand"
- equals(Object and hashCode() can get out-of-sync when introducing new instance fields without regenerating the code
Typical Use of Apache Commons Lang's Builders
The "typical use" of EqualsBuilder, as documented, is as follows:
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof MyClass == false) {
return false;
}
MyClass rhs = (MyClass) obj;
return new EqualsBuilder()
.appendSuper(super.equals(obj))
.append(field1, rhs.field1)
.append(field2, rhs.field2)
.append(field3, rhs.field3)
.isEquals();
}
Pros
- Flexible
Cons
- Lengthy
- May require some degree of unit testing, usually the reflexive test, null test and type test.
- Requires "maintenance" when adding new instance fields
- equals(Object and hashCode() can get out-of-sync when introducing new instance fields
- Third-party library dependency (this should not be an issue as Apache Commons Lang in ubiquitous...)
The Simplest Way to Override these methods
The possibly simplest way is to use the reflection-based methods of the Apache Commons Lang's Builders, as shown below:
@Override
public int hashCode()
{
return HashCodeBuilder.reflectionHashCode(this);
// or
// return HashCodeBuilder.reflectionHashCode(23, 13, this);
}
@Override
public boolean equals(Object obj)
{
return EqualsBuilder.reflectionEquals(this, obj);
// or, if transient fields are tested while some other fields should be excluded, and the reflection should be done
// up to a certain class (there are many overloading versions, so choose the right one)
// return EqualsBuilder.reflectionEquals(this, obj, true, Parent.class, new String[]{"excludedField1", "excludedField2"});
}
@Override
public String toString()
{
return ToStringBuilder.reflectionToString(this);
// or, if you'd like to choose a different style
// return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
Pros:
- No "maintenance" when adding new instance fields.
- Won't accidentally make hashCode() and equals(Object) out-of-sync when introducing new instance fields
- No need to write unit tests for these methods because they are too simple to break.
Cons:
- The overhead of reflection. But you shouldn't avoid these methods simply because of the overhead:
- Unless your class is used in big Collections and as key in big Maps, the overhead should be insignificant.
- Don't prematurely optimize. Wait until you identify this is where the bottleneck is using a profiler before you try a different implementation. In that case, don't forget to add unit tests first.
- Does not apply to entity objects directly. For these objects with a database identity, the equality test should be based only on database key, preferrably unique natural key.
So, if you think this is good, then why don't you put it in the code templates?
Embed in Code Templates
If you use IDEA, go to "Settings" -> "File Templates", select "Templates" tab and edit the content of "Class" to the following:
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
#parse("File Header.java")
public class ${NAME}
{
@SuppressWarnings("unused")
private static final Log log = LogFactory.getLog(${NAME}.class);
@Override
public int hashCode()
{
return HashCodeBuilder.reflectionHashCode(this);
}
@Override
public boolean equals(Object obj)
{
return EqualsBuilder.reflectionEquals(this, obj);
}
@Override
public String toString()
{
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
If you use Eclipse, select "Preferences", under "Java" -> "Code Style" -> "Code Templates", expand "Code".
Edit "New Java files" to something like the following:
${filecomment}
${package_declaration}
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
${typecomment}
${type_declaration}
Then edit "Class body" and change to the following:
@SuppressWarnings("unused")
private static final Log log = LogFactory.getLog(${type_name}.class);
@Override
public int hashCode()
{
return HashCodeBuilder.reflectionHashCode(this);
}
@Override
public boolean equals(Object obj)
{
return EqualsBuilder.reflectionEquals(this, obj);
}
@Override
public String toString()
{
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
Finally, if you use JAXB 2.x to generate Java classes from XML Schema, make sure you use jaxb2-commons's jakarta-commons-lang plugin to generate these methods. I have painful memory of working with plain JAB2-generated classes in unit testing before I discovered this precious plugin, which inspired me to write this post entry to share the simplest way to override hasCode(), equals(Object) and toString() methods with you....
1 comment:
All the pros and cons you have mentioned have made it easier for us to understand all the technicalities! Good stuff!
Phone Answering Services
Post a Comment