Skip to content

The “too lazy” equals()

2007 December 17
by Nayan Hajratwala

I’m a big fan of the Apache Commons Java libraries. Whenever I am about to write a method that I think someone probably should have made common by now, i usually hit up google with a query such as “apache commons capitalize“.

Recently, my favorite commons classes have been commons-lang EqualsBuilder, HashCodeBuilder and ToStringBuilder. These classes take the pain out of the usually tedious process of constructing their corresponding methods.

I’ve successfully used the following pattern for domain objects in a couple applications recently:

  • Define a base class DomainBase containing definitions of the three methods: equals(), hashCode() and toString() using the commons-lang Classes. Specifically, use the reflection version of the classes. i.e. return HashCodeBuilder.reflectionHashCode(this);
  • Have all your domain classes extend DomainBase. Voila! Great debugging capabilities (via toString), equality comparisons, etc.
  • I typically don’t worry about the “performance hit” that comes with reflection, unless it becomes a problem. Think premature performance tuning.

On my latest project, I’m using Hibernate for persistence and when one of my Unit Tests wasn’t running green, it occurred to me that I had forgotten to define the equals() and hashCode() methods. I decided to implement the pattern above. Bada-bing — tests running green.

All was well until I started adding one-to-many / many-to-one assoications to my domain model. As you may or may not know, Hibernate lazily loads these associations so it doesn’t have to make unnecessary SQL calls. The problem I was seeing was that when I added my domain objects to a HashMap, the hashCode() method would be invoked, which in turn would “reflect” and query the collection (i.e. make SQL calls to load the collections from the database).

I experimented with modifying my DomainBase hashCode() method to iterate through all the properties and excluding any collections/arrays that it encountered, but finally abandoned the approach because it was becoming unnecessarily complex.

In the end, I decided to keep the reflective toString() method in DomainBase, but manually define the equals() and hashCode() methods in each class that needed them.

Thankfully, my Unit Tests were there supporting me the whole way through, so I was free to change my method implementations without fear of breaking anything.

Update: I had to remove the reflective toString() method as well, since hibernate was trying to use it for outputting messages at debug logging level.