0

Let's say I have a person class and equality is based on id attribute. Below is the implementation of Person class -

class Person {

    private int id;
    private String firstName;

    public Person(int id, String firstName) {
        super();
        this.id = id;
        this.firstName = firstName;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public int hashCode() {
        return this.id;
    }

    public boolean equals(Object obj) {
        return ((Person) obj).getId() == this.id;
    }
}

I am using Person class as as key of a HashMap. Now see below code -

import java.util.HashMap;

public class TestReport {
    public static void main(String[] args) {

        Person person1 = new Person(1, "Person 1");
        Person person2 = new Person(2, "Person 2");

        HashMap<Person, String> testMap = new HashMap<Person, String>();
        testMap.put(person1, "Person 1");
        testMap.put(person2, "Person 2");

        person1.setId(2);

        System.out.println(testMap.get(person1));
        System.out.println(testMap.get(person2));

    }
}

Notice, though we have added two different person object as key to the HashMap, later we have changed the id of person1 object to 2 to make both the person object equal.

Now, I am getting output as -

Person 2
Person 2

I can see there are two key-value pairs in the HashMap with data: "person1/Person 1" and "person2/Person 2", still I will always get "Person 2" as output and I can never access value "Person 1". Also notice, we have duplicate key in HashMap.

I can understand the behavior after looking at the source code, but doesn't it seem to be problem? Can we take some precaution to prevent it?

Bernhard Barker
  • 50,899
  • 13
  • 85
  • 122
Kartic
  • 2,575
  • 5
  • 19
  • 39
  • 1
    you map is using a weird key, what is the goal you are trying to achieve with the map? –  Aug 10 '15 at 15:41
  • Refer Below link for understand " How HashMap work internally: http://howtodoinjava.com/2012/10/09/how-hashmap-works-in-java/ – Kiran Choudhary Aug 10 '15 at 15:41
  • @RC Yeah, I know it's weird. I did it intentionally for testing purpose. Also, I don't know what's the problem with this question. It's not always possible to identify it's a duplicate question. I am assuming this is the reason for down vote. – Kartic Aug 10 '15 at 15:58
  • 1
    I don't know either why you were downvoted (I don't think it's because of the duplicate, though) I counter balanced the downvote :o –  Aug 10 '15 at 16:08

2 Answers2

1

This is happening because your equals() method uses the hashcode only. You should be comparing all your person fields like firstName.

MahlerFive
  • 4,689
  • 4
  • 28
  • 40
  • Agree, I did it intentionally. My question is, don't we have an inaccessible key-value pair in the HashMap which we can never be accessed? Also in the example, we set the id to 3, we will get `null` as value. – Kartic Aug 10 '15 at 15:46
1

It all depends on how hashCode() value is used by HashMap.

While it is required that two equal objects of same hash code, reverse is not necessarily true. Two unequal objects can have same hash code (as int has only finite set of possible values).

Everytime you put an object in HashMap, it stores the object in a bucket identified by key's hashCode(). So, based on how hashCode() is implemented, you should have a fair distribution of entries in various buckets.

Now, when you try to retrieve a value, the HashMap will identify the bucket in which given key falls, and then will iterate through all keys in that bucket to pick the entry for given key - in this stage it will use equals() method to identify the entry.

In your case, person1 is sitting in bucket 1 and person2 is sitting in bucket 2.

However, when you changed the hashCode() value of person1 by updating its id, the HashMap is unaware of this change. Later, when you look up an entry using person1 as key, the HashMap thinks that it should be present in bucket 2 (as person1.hashCode() is 2 now), and after that when it iterates bucket 2 using equals method, it finds an entry of person2 and thinks that it is the object that you are interested in as equals in your case too is based on id attribute.

Above explanation is evident when one looks at implementation of HashMap#get method as shown below:

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

PS Sometimes when you know an answer to question, you forget to lookup for duplicate questions, and jump right into answering the question before anyone can reply - that's what happened in this case. Should be careful next time :-)

Wand Maker
  • 17,377
  • 6
  • 43
  • 79