Who calls the equals method of the class while putting the elements into the HashMap?

Viewed 620

I am new to Java (very new). I am trying to understand HashMap and the equals method of a class and how it overrides the duplicates. Please see following code:

public class Student {

    Integer StudentId;
    String Name;
    String City;

    public Student(Integer studentId, String name, String city) {
        super();
        StudentId = studentId;
        Name = name;
        City = city;
    }

    public Integer getStudentId() {
        return StudentId;
    }

    public String getName() {
        return Name;
    }

    public String getCity() {
        return City;
    }




    @Override
    public int hashCode() {
        System.out.println("haschode is called for " + this);

        final int prime = 31;
        int result = 1;
        result = prime * result + ((StudentId == null) ? 0 : StudentId.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("equals is called for " + this);

        if (this == obj)
            return true;

        if (obj == null)
            return false;

        if (getClass() != obj.getClass())
            return false;

        Student other = (Student) obj;

        if (StudentId == null) {
            if (other.StudentId != null)
                return false;

        } else if (!StudentId.equals(other.StudentId))
            return false;

        return true;
    } 


    @Override
    public String toString() {
        return "\n Student [StudentId=" + StudentId + ", Name=" + Name + ", City=" + City + "] \n";
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub


         Map<Student, String> myMap = new HashMap<Student, String>();

         myMap.put(new Student(1, "andy", "p"), "Great");     //Line 1
         myMap.put(new Student(2, "sachin", "m"), "Better");
         myMap.put(new Student(3, "dev", "s"), "Good");
         myMap.put(new Student(1, "andy", "p"), "Excellent"); // Line 4

         System.out.println(myMap);


    }

}

Now, the code written in main() calls the equals method only when I write the code to put the same key again i.e. "Line 4" (see my code indentation).

Why is the equals method not called for "Line 2" and "Line 3"?? It should call equals for every put line .... correct?

I am missing some understanding here and am left with questions: (1) Why is every put not calling the equals method to check the equality of class members? (2) Who triggers the call of the Student class equals method?

7 Answers

It should call equals for every put line .... correct ?

No. A HashMap will call equals only after it encounters a hash collision between an existing key and the one given in put.

Rephrased, it calls hashCode first to determine which "hash bucket" to put the key into, and if there are already keys inside the target bucket, it then uses equals to compare the keys in the bucket for equality.

Since the value of Student.hashCode() is based on ID alone, during insertion, the map only needs to call equals when it encounters a Student key with the same ID as what is being inserted. If no existing keys have the same hashCode as the one being inserted, there is no need to call equals.

This makes HashMap very efficient during insertion. This is also why there is a contract between hashCode and equals: If two objects are equal as defined by equals, they must also have the same hashCode (but not necessarily vice-versa).

equals() is not called if the hashCode() result is different. It's only the same for Line 1 and Line 4 (same student Id of 1), so equals() is called for that.

Note that hashCode() may be the same for two objects that aren't equals(), but two equals() objects must never have a different hashCode():

  • 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(java.lang.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 hash tables.

So the initially different hash code is enough to not call equals() afterwards.

The whole purpose of a hash-based map is to operate on hash values (for efficiency that is).

The Map first and foremost cares about different hash values. Thus, as long as any "incoming" key has an (so far) unknown hash, equality doesn't matter.

Only when you run into a conflicting hash, then it matters whether that incoming key is actually a different key, or the same key. In the first case, you add a new key/value pair to the map, in the second case, you update an already stored key with a potential new value!

Therefore calling equals() only happens for situations where the Map implementation has to decide whether two keys that have the same hash are equal, or not.

If hash code differs, then there is no case for calling equals. Look at the code for HashMap(). If the hash is the same, then equals is called.

As you can see while running your code, hashcode() is called for every .put() call.

Basicly hashcode is called for every put() operation, if it is unique then a new element can be placed in the map - as one of the conditions for the hashcode() says, that different hashcodes always represent different objects. However, different object don't always have different hashcodes. Because of that, if the hashcodes are the same for two objects, hashmap have to check object equality with equals().

If you want to watch how the hashCode and equals methods work for selected values, create the following map:

Map<Key, Value> map = new HashMap<>();

Then create instances of the following classes and use them to populate the Map. I recommend using a String as the object of both classes since I used its equals method in both.

Notice that you supply the hashCode to be returned. This allows it to be the same or different so you can see how the map behaves in different situations.

class Value {
   private Object obj;
   private int hashCode;

   public Value(Object obj, int hashCode) {
      this.obj = obj;
      this.hashCode = hashCode;
   }

   public int hashCode() {
      System.out.println("Value: hashCode is called - " + hashCode);
      return hashCode;
   }

   public boolean equals(Object o) {
      System.out.println("Value: equals is called - " + obj);
      return obj.equals(o);
   }

   public String toString() {
      return "Value: obj = " + obj + ", hashCode = " + hashCode;
   }
}
class Key {
   private Object obj;
   private int hashCode;

   public Key(Object obj, int hashCode) {
      this.obj = obj;
      this.hashCode = hashCode;
   }

   public int hashCode() {
      System.out.println("Key: hashCode is called - " + hashCode);
      return hashCode;
   }

   public boolean equals(Object o) {
      System.out.println("Key: equals is called - " + obj);
      return obj.equals(o);
   }

   public String toString() {
      return "Key: obj = " + obj + ", hashCode = " + hashCode;
   }
}

You can read the source code of HashMap.java in JDK 1.7.

Than you will understand the questions you asked.

Other answers are more helpful after you have reading the source code of HashMap.

Related