Java USES custom classes as instances of key values for HashMap

  • 2020-05-19 04:57:23
  • OfStack

This is a classic question in Java and is often asked in interviews. Many books and articles have mentioned overloading the hashCode() and equals() methods to find custom keys in HashMap, but it seems that there are few articles on why and what happens if you don't, so I'll write this one to explain.

First of all, what happens if we directly use the following Person class as the key and store it in HashMap?


public class Person {

  private String id;

  public Person(String id) {
    this.id = id;
  }
}
import java.util.HashMap;

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

    HashMap<Person, String> map = new HashMap<Person, String>();

    map.put(new Person("001"), "findingsea");
    map.put(new Person("002"), "linyin");
    map.put(new Person("003"), "henrylin");
    map.put(new Person("003"), "findingsealy");

    System.out.println(map.toString());

    System.out.println(map.get(new Person("001")));
    System.out.println(map.get(new Person("002")));
    System.out.println(map.get(new Person("003")));
  }
}

So what is the output?


{Person@6e4d4d5e=henrylin, Person@275cea3=findingsea, Person@15128ee5=findingsealy, Person@4513098=linyin}
null
null
null

As you can see, there are two problems here:

1. In the process of adding, we added key=new Person("003") key value pair twice, so in the expectation, only 1 pair of such key value pair should exist in HashMap, because key (" expectation ") is the same, so it should not be added repeatedly. The second addition of value="findingsealy" should replace the original value="henrylin". However, in the input, we found that the expected situation did not occur, but two key value pairs of value="findingsealy" and value="henrylin" existed in HashMap at the same time, and their key values were not the same, which is obviously wrong.

2. When obtaining the value value, we respectively used three Person objects to search for it. These three objects are the same as the three key values we just stored (in the expectation), but we found three null values, which is obviously wrong.

Therefore, the correct method has been described in many places. Directly modify the Person class, overload the equals and hashCode methods, and the modified Person class is as follows:


public class Person {

  private String id;

  public Person(String id) {
    this.id = id;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Person person = (Person) o;

    if (id != null ? !id.equals(person.id) : person.id != null) return false;

    return true;
  }

  @Override
  public int hashCode() {
    return id != null ? id.hashCode() : 0;
  }
}

Then, when we re-execute the above inspection procedure, the results are as follows:


{Person@ba31=findingsea, Person@ba32=linyin, Person@ba33=findingsealy}
findingsea
linyin
findingsealy

As you can see, the highlighted errors have been corrected. So why is that?

In HashMap, the comparison order to find key is:

1. Calculate the Hash Code of the object and see if it exists in the table.

2. Check whether the object in the Hash Code position is equal to the current object.

Obviously, step 1 is going to use the hashCode() method, and step 2 is going to use the equals() method. When there is no overloading, the two methods of Object class will be called by default in these two steps. In Object, the calculation method of Hash Code is calculated according to the address of the object. The object addresses of the two Person("003") are different, so their Hash Code are different, and naturally HashMap will not regard them as the same key. Also, in Object's default equals(), which is also based on the object's address, naturally one Person("003") is not equal to another Person("003").

With this point in mind, it's easy to see why you need to overload both the hashCode() and equals methods.

The & # 8226; Overloading hashCode() is to get the same Hash Code for the same key, so that HashMap can be located on the key we specified.

The & # 8226; Overloading equals() is to show HashMap that the current object is the same as the object stored on key, so that we actually get this key value pair for key.

One more detail, the key method for hashCode() in the Person class is:


@Override
public int hashCode() {
  return id != null ? id.hashCode() : 0;
}

One possible point of confusion here is: why can Hash Code of a variable of type String be used as the Hash Code value of class Person? Is new Person(new String("003")) the same as Hash Person(new String("003"))?

Take a look at the output of the following code:


System.out.println("findingsea".hashCode());
System.out.println("findingsea".hashCode());
System.out.println(new String("findingsea").hashCode());
System.out.println(new String("findingsea").hashCode());
728795174
728795174
728795174
728795174

As you can see, the output of all four statements is equal, and a reasonable guess is that the String type also overloads hashCode() to return the Hash Code value based on the content of the string, so strings with the same content have the same Hash Code.

At the same time, this also explains a question: why do we need to compare equals() when we know that hashCode() is equal? Just to avoid the situation in the above example, because according to the overloaded implementation of hashCode() method of Person class, Person class will directly use Hash Code value of id String type member as its Hash Code value, but obviously, 1 Person("003") and 1 String("003") are not equal, so if hashCode() is equal, You also need to compare it with equals().

The following examples can be used to support the above explanation:


System.out.println(new Person("003").hashCode()); // 47667
System.out.println(new String("003").hashCode()); // 47667

System.out.println(new Person("003").equals(new String("003"))); // false

Related articles: