Back to Basics: hashCode() & equals()

December 6th, 2011 by David Kessler Leave a reply »

So we all know that if we implement equals() we must override hashCode() too. But why? The best explanation of this commandment can be found in Effective Java (2nd Edition) starting on page 45.


… Failure to do so will result in a violation of the general contract for Object.hashCode, which will
prevent your class from functioning properly in conjunction with all hash-based
collections, including HashMap, HashSet, and Hashtable. (Bloch, Effective Java, 2nd Ed.)


If you’re like me, you like to see a problem in running code. Well here you go.

	@Test
	public void testHashMapRetrieval_WithoutHashCode(){
		PersonWithoutHashCode person = new PersonWithoutHashCode("first", "last");
		PersonWithoutHashCode person2 = new PersonWithoutHashCode("first", "last");
		Map<PersonWithoutHashCode, String> map = new HashMap<PersonWithoutHashCode, String>();
		map.put(person, "friend");
 
		assertNull(map.get(person2));
	}
 
	@Test
	public void testHashMapRetrieval_WithHashCode(){
		PersonWithHashCode person = new PersonWithHashCode("first", "last");
		PersonWithHashCode person2 = new PersonWithHashCode("first", "last");
		Map<PersonWithHashCode, String> map = new HashMap<PersonWithHashCode, String>();
		map.put(person, "friend");
 
		assertNotNull(map.get(person2));
	}

Two equivalent ‘PersonWithoutHashCode’ instances are not considered equivalent when the ‘get’ method is called on HashMap. On the other hand the ‘PersonWithHashCode’ instances are considered equivalent for the same method.

While you can implment equals() and hashCode() by hand or generate them with your IDE, you might consider using the Objects class within the Guava Libraries. Here is the Person class using Guava.

	@Override
	public int hashCode() {
		return Objects.hashCode(firstName, lastName);
	}
 
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		PersonWithGuava other = (PersonWithGuava) obj;
		return Objects.equal(firstName, other.firstName) 
				&& Objects.equal(lastName, other.lastName);
	}

Here’s the test for this new code.

	@Test
	public void testHashMapRetrieval_WithGuava(){
		PersonWithGuava person = new PersonWithGuava("first", "last");
		PersonWithGuava person2 = new PersonWithGuava("first", "last");
		Map<PersonWithGuava, String> map = new HashMap<PersonWithGuava, String>();
		map.put(person, "friend");
 
		assertNotNull(map.get(person2));
	}

So why use this? This helps make our code DRY. Much of the generated code for the ‘hashCode()’ and ‘equals()’ method can be encapsulated. The Guava Library has done just that for us. If the implementation needs to be changed it can be updated in one place.

Now if you’re like me, you are lazy. As a lazy person I ask myself, “Do I want to write code that I can generate?”. My initial thought is “no”. Luckily you can use this new library and continue to generate your ‘equals()’, ‘hashCode()’ and even your ‘toString()’ methods. Simply add the Guava Eclipse plugin to your favorite version of Eclipse and voila.

This plugin adds new menu options.

menu

wizard

This is the code that the plugin generated for the Person class.

	@Override
	public int hashCode(){
		return Objects.hashCode(firstName, lastName);
	}
 
	@Override
	public boolean equals(Object object){
		if (object instanceof PersonWithGuava) {
			PersonWithGuava that = (PersonWithGuava) object;
			return Objects.equal(this.firstName, that.firstName)
				&& Objects.equal(this.lastName, that.lastName);
		}
		return false;
	}

While we all know that we must override ‘hashCode()’ and ‘equals()’ together, we also know that we should eliminate duplication in our code and encapsulate common functionality. Install the Guava plugin today and remove another piece of duplication from your code.

Please read the comments below before you use this plugin.

Here’s the complete code HashCode.

Advertisement

5 comments

  1. Akrem Saed says:

    Very powerful post, I didn’t know what to think about Guava before but now I’m impressed. Nice job, David.

    And here is one more point about equals implementation. I see you used “instanceof” test which has the implication that you want a subclass object be considered the same as the superclass object if it has the same field values used in the equals test. This can lead to equality unsymmetry where if a subclass overwrites the equals method using “if (object instanceof PersonWithGuavaSubclass)” you will run into situation where subObject.equals(superObject) will always be false but superObject.equals(subObject) might return with true.

    I find it sometimes helpful to NOT consider them the same by testing equality on the actual class names of the subclass and superclass.

    This approach, will help insure symmetry where if a subclass overwrites the equals method to use some new additional fields in the test, you can ensure that subObject.equals(superObject) is the same as superObject.equals(subObject).

  2. David Kessler says:

    Great catch. The default Eclipse generate hashCode() & equals() defaults to class comparison with an option to use instanceof comparison instead. I’ve posted a feature request back to the Guava Plugin project. http://sourceforge.net/tracker/?func=detail&aid=3485014&group_id=388911&atid=1615624. It is worth noting that I found the following options in the Guava Preferences within Eclipse.

    • - Use super class Methods(toString(), equals() and hashCode())
    • - Don’t use super class Methods (toString(), equals(), and hashCode())
    • - Use super class Methods (Only if superclass is not “java.lang.Object”)

    The third option is the default setting. I think this is very helpful. Nevertheless, these options do not eliminate the need for class comparison. Without a class comparison option the generated code can still violate symmetry. This plugin should err on the side of caution by using class comparison by default. ‘instanceof’ comparison should be an option rather than the default. Thank you for your excellent comments.

  3. Hugh says:

    There are good ideas in this article .. This site is one of my favorite

  4. Adriana says:

    thanks for share!

Leave a Reply

*