Don't Forget Hashcode!

April 30, 2017

Don’t forget about .hashCode!

In the Java world, .hashCode doesn’t get mentioned much. I’ve rarely seen it implemented in otherwise standard POJOs and, now that I’ve learned a trick or two, I can’t fathom why.

Why use it?

Collection.contains. That’s why.

How often do you find yourself searching through an array, checking to see if it contains an element that matches one you have? Not never, most likely. And as long as the element you’re checking for is the same instance as the one in the Collection, you’re fine; no need for hashcode, necessarily.

But consider this:

MyClass item1 = new MyClass("hello", "world");
MyClass item2 = new MyClass("hello", "world");
List<MyClass> list = Collections.singletonList(item1);

Assert.true(list.contains(item2)); // CRASH!

The result may or may not surprise you, depending on what you’re used to. The assert will fail, because .contains internally uses .equals, which, by default, basically does a reference check. That is to say, it checks if item1 is a reference to the exact same object as item2, rather than if it contains the same values.

If we changed the item2 declaration to MyClass item2 = item1 and ran list.contains(item2), it would return true. In that case, we’d have two variables, but only a single object in memory, which is what .contains/.equals checks for.

What if we want to change that?

Firstly, the default behavior may well be exactly what you want. You don’t need to blindly implement .hashCode. But I’ve found that when I’m checking for equality, what I really want to know is whether there’s an item with the same values, regardless of whether it’s the same instance.

To change this, we should implement two methods; equals and hashCode. Technically, .equals would be enough on its own, but the two go together. If one returns true, then the other should return true, and vice versa. Implementing only .equals would violate that and lead to unexpected behavior (aka bugs) down the line.

Ok, now for the fun part. There’s a not-very-secret-but-I-didn’t-know-about-it-and-nobody-else-I-know-did-either way to implement .hashCode. Here goes:

@Override public int hashCode() {
    return java.util.Objects.hash(property1, property2);
}

You were expecting something fancier, weren’t you? Nope. This is so easy, a Java programmer could do it. The .equals is just as easy:

@Override public boolean equals(Object o) {
    return o instanceof MyClass && o.hashCode() == this.hashCode();
}

And there you have it. Throw those into MyClass and our Assert up there will finally return true.

Special case: Arrays

If your class has an array, you can’t just throw it into Objects.hash; different lists with elements that equal each other will have different hashcodes. For arrays, add Arrays.hashCode(yourList), like so:

Objects.hash(Arrays.hashCode(yourList), prop1, prop2, prop3);

Special case: Kotlin

If you are using Kotlin, there’s an easier way to do it than in java, as you’ve probably come to expect by now. data classes automatically generate toString, copy, hashCode, and equals for you; as long as your class is a data class, you’re good to go.

I have created a snippet of runnable Kotlin code with different ways of using hashcode, along with whether they are equal or not.

http://try.kotlinlang.org/#/UserProjects/9mgchmsmbb427n7osbp3s4dmet/tt4l4cmelbl7u96u8pdq8rapsj


Follow @davidwhitman    Star    Issue