post-images/overriding-hashcode/cover.jpg

Overriding hashCode()

Hello my friend, it's me again 😎 and we continue with the second article of the new unit. In this article, we will cover the second item of Methods Common to All Objects, which is the second unit of the Effective Java book.

In this article, I will talk about how to override the hashcode() method in the Object class, under which conditions it should be overridden, and the critical errors that we need to pay attention to.

Let's visit our Object class, what's in this hashCode().

/**  
 * Returns a hash code value for the object. This method is 
 * supported for the benefit of hash tables such as those provided by 
 * {@link java.util.HashMap}.
 */
public native int hashCode();

When I look at this method in the Object class, I see that it does not have any implementation. So what does this method do? Actually, when I want to generate a hash code for my class, I need to override this method and generate a hash code for my object. So when should I use the hashCode() method? If the equals() method is overridden, also it must be overridden hashCode. So what's the deal, why is there such a need? Don't worry, I'll look into it in more detail. You might want to take a look at my post on Overriding equals() before moving on to the review.

Just like the equals() method, the hashCode() method also has its own contract. While implementing hashCode(), I should not neglect these items. I will explain the reason after the contract clauses.

  • The hashCode() method that I override within an object should produce the same value every time it is called. This should be maintained as long as the controls in equals() do not change.
  • If two objects are equal according to the equals() method, the hashCode() method of both objects should produce the same integer value.
  • Contrary to the second item, if two objects are not equal according to the equals() method, the hashCode() method of both objects does not need to generate different integers. But generating different numbers increases the performance of hashTable.

At this stage, to talk about what hash code is, data structures such as HashMap, HashSet, HashTable refer to the location of the object in the current memory while calculating the hash code with the mentioned hashCode().

Let me quickly move on with the rule violation. If I implemented the equals() method but never touched the hashCode() method; When I use my objects that are equal according to the 'equals()' method in data structures such as 'HashMap', 'HashSet', 'HashTable', these same objects will appear as two different objects. This indicates that I have violated my second item.

Let me reinforce it with an example:

public class PhoneNumber {

    private int areaCode;
    private int prefix;
    private int lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof PhoneNumber))
            return false;

        PhoneNumber pn = (PhoneNumber) o;
        return pn.lineNumber == lineNumber &&
            pn.prefix == prefix &&
            pn.areaCode == areaCode;
    }

    // No hashCode() method!
}

Our class above seems to be okay in terms of the equals method. But what if there is no hashCode() , what is the problem here? Let's try generating instances for the class...

Map<PhoneNumber, String> map = new HashMap<>();  
map.put(new PhoneNumber(707, 867, 5309), "Jenny");

It's ok. I have a map entry with Key = new PhoneNumber(707, 867, 5309), Value = "Jenny", right?

map.get(new PhoneNumber(707, 867, 5309));

Yesss we'll see "Jenny" reply... I would like to say but unfortunately, it will return null. In the equals() method, these two objects are equal to each other. The check for new PhoneNumber(707, 867, 5309).equals(new PhoneNumber(707, 867, 5309)) will return the value true. Note that while equals() checks the logical value, hashCode() looks at the memory reference.

Of course, the way to solve this problem is the same as the subject we examined in the article 😊 So how can we write the hashCode() method by the contract clauses. At this stage, it is not difficult to comply with the contract clauses, but it is quite difficult to produce different hash codes. In fact, the example below fits the conventions, but it's a very bad hash code implementation.

// The worst possible legal hashCode implementation - never use! 
@Override public int hashCode() { return 53; }

✅ Does our object's hashCode() method return the same value each time it is called?
✅ Are the hash codes of equal objects checked in equals() the same?
✅ The hash codes of unequal objects checked in equals() do not have to be the same.

As you can see, every item of the contract is carried out. But to be able to say that I have produced a good hash code, its values must be largely different from each other. If it is not different, it causes huge performance losses. It causes HashTable, which I mentioned in the 3rd article of the contract before, to run 4 times slower by running it like LinkedList. A few sample hashCode() implementations are as follows;

  • Standard hashCode() implementation (best performance):
@Override  
public int hashCode() {
    int hash = 7; 
    hash = 31 * hash + (int) id; 
    hash = 31 * hash + (name == null ? 0 : name.hashCode()); 
    hash = 31 * hash + (email == null ? 0 : email.hashCode()); 
	
    return hash; 
}
  • I can also use the properties of the object:
@Override  
public int hashCode() {
    return (int) id * name.hashCode() * email.hashCode(); 
}

Alternatively, you can use the @AutoValue annotation. It will automatically generate value for you.

I think we agree that my friends who override the equals() method for their own class should show the same sensitivity about hashCode(), right?

See you in the next article 🖖🏼

JavaEffective JavaMethods Common to All ObjectshashCode()OverridingSoftware ContractAutoValue