How to implement a Hibernate-safe equals() method using instanceof and accessors (getters) in Eclipse

When coding in Java you often want to check to see if two objects are 'equal'. This might range from a check to see if just one attribute is the same (the ID for example), or a complete comparisons of all attributes all the way up to ensuring that two objects are referencing the same instance of a particular class.

There is a lot of information out on the internet about how to write a correct equals() method (and the corresponding hashCode()) but I think Joshua Bloch's explanation from his excellent Effective Java book is the best.

Here are some tips that I learnt the hard way today when trying to test some JPA/Hibernate-backed objects for equality:

  • Don't use the default settings of Eclipse's built-in generator for equals() and hashCode() - it generates equals methods based on checking whether the two objects are from the same class (using getClass()), Hibernate proxies everything so this will never match

  • Don't use Eclipse's built-in generator at all - it's alternative option to "Use 'instanceof' to compare types" is more correct than it's default but still doesn't work for Hibernate classes

  • Do ensure that instanceof is used when ever checking if two objects have a similar background, the Hibernate proxies seem to respect this check so the proxies are instances of the classes that they proxy.

  • Do ensure that you use the accessors (getters) when getting the member variable values rather than the variables directly as the values maybe lazy loaded so id may actually return null whereas getId() will return the actual value from the database.
Feel free to use Eclipse's generator (with the instanceof option selected) as a starting point (I do) but don't forget to add in the extra bits described above.

So go from this:
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Person))
return false;
final Person other = (Person) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
to this:
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Person))
return false;
final Person other = (Person) obj;
if (getId() == null) {
if (other.getId() != null)
return false;
} else if (!getId().equals(other.getId()))
return false;
if (getName() == null) {
if (other.getName() != null)
return false;
} else if (!getName().equals(other.getName()))
return false;
return true;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
return result;
}
A blog post from wenhu on the subject as well as some information from my colleagues were the sources for this post and the remedy to today's headache!

Technorati Tags: , , , , , , ,

4 comments:

Nhoj Yelruc said...

Hi, Andrew,

What if I don't like hash. I really don't like the taste. I prefer haggis.

Ergo, if I don't like hash do I have to write a hashCode() method?

This may be a bit too much personal information: when I ate hash in the past, it used to get stuck in my Nokia phone. Only the receiver portion.

(In a way, it was good. I could still talk but didn't have to listen.)

Sincerely,
Nhoj Yelruc

Chetan said...

Not sure whether you want to use id as part of your equals/hashcode (if its generated by hibernate)...in fact thats considered to be a bad practice. Your object should be deoendent on a business key

Anonymous said...

You can omit the check for null,
because (obj instaceof MyClass) returns false anyway in case obj has value null, no matter its type.

Maarten Hogendoorn, JConsultancy.com

ramrajedotcom said...

if this.id and this.name are null, and other object has these values. equals should return false, but your implementation returns true, wrong! Have logical boolean condition with all business key fields.

In BTW, having id in equals is not a good idea. Always have business key fields (i.e fields which makes object unique)