Jul 12, 2008

Be careful with your 'hashCode()'!

Every Java developer know that there are some methods common for all objects. One of them is 'hashCode()'. By default it returns internal address of the object, but should be overridden when your overrides another common method 'equals()'. Contract for the 'hashCode()' is following:

  • Whenever it is invoked on the same object more than once during
    an execution of a Java application, the hashCode method
    must consistently return the same integer, provided no information
    used in equals comparisons on the object is modified.
    This integer need not remain consistent from one execution of an
    application to another execution of the same application.

  • If two objects are equal according to the equals(Object)
    method, then calling the hashCode method on each of
    the two objects must produce the same integer result.

  • It is not required that if two objects are unequal
    according to the equals(Object) method, then calling the
    hashCode method on each of the two objects must produce
    distinct integer results. However, the programmer should be aware
    that producing distinct integer results for unequal objects may
    improve the performance of hashtables.



From this contract you can draw a conclusion that 'hashCode()' implementation should use only immutable fields of the object. Why? Lets create user domain object:

public class User {
private Long id;
private String name;

public User(String name) {
this.name = name;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public boolean equals(Object obj) {
return (this == obj) ||
(obj instanceof User && equals((User) obj));
}

public boolean equals(User that) {
EqualsBuilder builder = new EqualsBuilder()
.append(id, that.id)
.append(name, that.name);
return builder.isEquals();
}

public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder()
.append(id)
.append(name);
return builder.toHashCode();
}
}

I have used commons-lang to generate 'equals()' and 'hashCode()' but it doesn't matter. Now lets test if our 'hashCode()' is correct by running JUnit test:

public class UserTest {
@Test
public void testMutableName() {
Set users = new HashSet();
User user = new User("user");
user.setId(5L);
users.add(user);
user.setName("user2");
Assert.assertTrue(users.contains(user));
}
}

This test will fail because hash code of the user object was changed while user object itself was saved in the collection that uses it to save objects more efficiently.

Try to include in the 'hashCode()' only immutable fields, because you can have hidden issues not only with your own code but also with frameworks like Hibernate. If you don't have immutable fields, then be very careful and take described issues into account (recreate objects instead of changing them, don't use long live collections, return constant from 'hashCode()' or 'ID' field value). I hope this article will save you time for debugging. Develop with pleasure!

No comments: