Equals and hashCode are two methods which are very important for various data structure and for Hibernate as well. You will find a lot of nonsense about equals and hashCode, therefor I will take greatest care to provide you with a good explanation.
In Java you can compare two objects using ==. The comparison returns true, if both objects have the same memory address.
if(foo == bar) log.debug("Foo has the same memory address as bar");
The equals method
The equals method is implemented by java.lang.Object and, if not overridden, will behave as ==. The intention of equals is to compare objects from a business point of view. For example a class Country has a field isocode like DE for Germany or FR for France etc. We could consider to compare two countries using the isocode.
public class Country { private String isocode; private String description; private int population; public String getIsocode() { return isocode; } @Override public boolean equals(Object o) { if(o instanceof Country){ Country other = (Country) o; return isocode.equals(other.getIsocode()); } return false; } }
Good candidates for equals are fields which could be used as natural primary key or alternatively are having a unique key constraint in the database.
There are the following rules to respect when implementing the equals method:
The hashCode method
If you use your class with structures calling equals and hashCode - for example a HashSet or a HashMap - then you should override both methods. Their behaviour depend on each other. The hashCode computes a unique int value for your class. It does not need to be perfectly unique but should be well distributed. Let’s have a look at an example.
A java.util.HashSet is a structure guarantying that only one instance of an object is included. It consists of buckets (simplified!). The following code will add a new country instance to a HashSet. Internally, the HashSet will compute the hash code and find a bucket using a modulo operator. The country is added to the bucket under some conditions.
Set<Country> countries = new HashSet<Country>(); countries.add(new Country("DE", "Germany"));
If the HashSet has 16 buckets then the bucket is calculated hashCode % 16. If there are already entries in a bucket, then hashCode will compare all entries using the equals method. If no existing entry is equal, then the item will be added to the bucket. Therefor, if we implement hashCode, we should implement equals as well.
The relation between equals and hashCode is: If equals returns true, then hashCode must return the same value. Otherwise a structure like java.util.HashSet won’t find the same bucket and could not guaranty that there are no two equal objects in a HashSet.
This relation is not equivalent. If the hash code of two objects are the same, then the two objects don’t have to be equal but they can be equal.
Country with equals and hashCode.
public class Country { private String isocode; private String description; private int population; public String getIsocode() { return isocode; } @Override public boolean equals(Object o) { if(o instanceof Country){ Country other = (Country) o; return isocode.equals(other.getIsocode()); } return false; } @Override public int hashCode() { return isocode.hashCode(); }
Further more, the computed hash code should not change while a structure contains the object. Have a look at the following code. Though we added the country object to the HashSet, we cannot find it any more, as the hashCode changes if we set the isocode to a different value.
Set<Country> countries = new HashSet<Country>(); Country country = new Country("DE", "Germany"); countries.add(country); country.setIsocode("FR"); System.out.println(countries.contains(country)); // prints most likely false
The hashCode should be well distributed to let structures distribute the objects on buckets well distributed.
You can change an object such that the hashCode changes but not while the object is in a Set or Map structure and you or Hibernate is using the structure.
Always implement equals if you implement hashCode.
Consider to let your IDE generate equals and hashCode for you. Eclipse, IntelliJ and Netbeans are capable to generate it. You just need to know which fields to use → unique keys or business keys or natural primary key candidates.
Read the book Effective Java from Joshua Bloch for further details.
Hibernate and equals
To answer the most important question first: Do I have to implement equals and hashCode?
Answer: It depends.
A composite id must implement both methods or the id will not work properly. Hibernate must be able to compare ids. A composite id will most likely use all its id fields in the equals and hashCode methods.
An entity does not need to implement equals and hashCode under some conditions. The persistence context guaranties that an entity with a given id exists only once. You cannot let two objects with the same id become persistent. Assuming that a class Country uses the isocode as ID, the following code will cause a non unique object exception.
Country first = new Country(); first.setIsocode("DE"); session.save(first); Country second = new Country(); second.setIsocode("DE"); session.save(second); // NonUniqueObject exception
Therefor, if you save or update or merge your objects before adding them to a Set or Map, then you do not need to implement equals and hashCode.
But if you want to compare your objects or add them to a Set or Map, when the session is closed and the object is detached, then you must implement equals and hashCode. If you do not compare your objects with each other or do not use a Set or Map, then you do not have to implement equals and hashCode.
If you implement equals, then you should use all fields which are either business keys, unique key constraints in the database or natural primary key candidates. In many cases all three conditions are the same.
If you do not have a unique key, then you could use the id as business key, if you are careful. The simple rule is: do always save your objects before adding them to a Set. Saving an object will generate the id (if generated).
A sample implementation.
class Country{ @Id @GeneratedValue private Integer id; @Override public boolean equals(Object o) { if(o instanceof Country){ Country other = (Country) o; return id.equals(other.getId()); } return false; } @Override public int hashCode() { return id.hashCode(); } }
When implementing equals you should use instanceof to allow comparing with subclasses. If Hibernate lazy loads a one to one or many to one relation, you will have a proxy for the class instead of the plain class. A proxy is a subclass. Comparing the class names would fail.
More technically: You should follow the Liskows Substitution Principle and ignore symmetricity.
The next pitfall is using something like name.equals(that.name) instead of name.equals(that.getName()). The first will fail, if that is a proxy.
That should be all you need to know, I hope.