32

When I run this code why only hashCode() is called not equals method while my hashCode() implementation generate same hashCode for both entries to HashSet?

import java.util.HashSet;

public class Test1 {
    public static void main(String[] args) {
        Student st=new Student(89);
        HashSet st1=new HashSet();
        st1.add(st);
        st1.add(st);
        System.out.println("Ho size="+st1.size());
    }
}
class Student{
    private int name;
    private int ID;
    public Student(int iD) {
        super();
        this.ID = iD;
    }
    @Override
    public int hashCode() {
        System.out.println("Hello-hashcode");
        return ID;
    }
    @Override
    public boolean equals(Object obj) {
        System.out.println("Hello-equals");
        if(obj instanceof Student){
            if(this.ID==((Student)obj).ID){
                return true;
            }
            else{
                return false;
            }
        }
        return false;  
    }
}

The output for this is:

Hello-hashcode
Hello-hashcode
Ho size=1
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
san9w
  • 333
  • 1
  • 3
  • 8

5 Answers5

26

The hash set checks reference equality first, and if that passes, it skips the .equals call. This is an optimization and works because the contract of equals specifies that if a == b then a.equals(b).

I attached the source code below, with this check highlighted.

If you instead add two equal elements that are not the same reference, you get the effect you were expecting:

    HashSet st1=new HashSet();
    st1.add(new Student(89));
    st1.add(new Student(89));
    System.out.println("Ho size="+st1.size());

results in

$ java Test1
Hello-hashcode
Hello-hashcode
Hello-equals
Ho size=1

Here's the source code from OpenJDK 7, with equality optimization indicated (from HashMap, the underlying implementation of HashSet):

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
//                                         v-- HERE
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}
that other guy
  • 116,971
  • 11
  • 170
  • 194
  • ^ this. I commented below with the same thing, but I guess we were submitting at the same time. I didn't think giving a full code explanation was necessary. It would just be silly to do a logical equality check if the object reference (the reference is pointing to the same memory) is the same. – Kyle Richter Aug 15 '14 at 18:34
9

A HashSet uses a HashMap as its backing mechanism for the set. Normally, we would expect that hashCode and equals would be called to ensure that there are no duplicates. However, the put method (which calls a private putVal method to do the actual operation) makes an optimization in the source code:

if (e.hash == hash &&
    ((k = e.key) == key || (key != null && key.equals(k))))

If the hash codes match, it first checks to see if the keys are the same before calling equals. You are passing the same Student object, so they are already ==, so the || operator short-circuits, and equals is never called.

If you passed in a different Student object but with the same ID, then == would return false and equals would be called.

rgettman
  • 176,041
  • 30
  • 275
  • 357
4

Looking through the source code of HashSet it is using HashMap for all of its operations and the add method performs put(element, SOME_CONSTANT_OBJECT). Here is the source code for the put method for JDK 1.6.0_17 :

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

as you can see it performs a == comparison before using the equals method. Since you are adding the same instance of an object twice the == returns true and the equals method is never called.

SamYonnou
  • 2,068
  • 1
  • 19
  • 23
3

Equals is always called after the hashCode method in a java hashed collection while adding and removing elements. The reason being, if there is an element already at the specified bucket, then JVM checks whether it is the same element which it is trying to put.

hashcode() and equals() method

Community
  • 1
  • 1
Liam
  • 2,837
  • 23
  • 36
  • Is the java api incorrect then? From 'add': "...adds the specified element e to this set if this set contains no element e2 such that (e==null ? e2==null : e.equals(e2)). " – damian Aug 15 '14 at 18:24
  • 3
    ? 'Hello equals' doesn't get called in the OP's code. for this to be an answer shouldn't this explain why? – Nathan Hughes Aug 15 '14 at 18:29
  • 1
    You're not calling .equals, because the reference checks dictate that the object reference is the same, so there's no need to run logical equality checks. – Kyle Richter Aug 15 '14 at 18:30
3

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.

Himanshu Sharma
  • 79
  • 1
  • 4
  • 12