Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

Let’s take a look at a passage from Alibaba’s Java development manual:

HashCode must be overridden whenever you override equals. 2) Since Set stores non-repeating objects according to hashCode and equals, Set objects must override these two methods. 3) If a custom object is used as a Map key, then hashCode and equals must be overridden. Note: String overrides the hashCode and equals methods, so we can happily use String objects as keys.

It requires us to force hashCode to be overridden if we override equals. Why?

Equals and hashCode methods

Let’s look at these two methods. They both come from the Object class, so there are two methods in every class. What do they do?

The first is the equals method, which compares whether two objects are equal. For the use of equals, the score is discussed. If a subclass overrides equals, it will be compared according to the overridden rules, such as:

public static void main(String[] args) {
    String s = "hello";
    String str2 = "world";
    boolean result = s.equals(str2);
    System.out.println(result);
}
Copy the code

Let’s look at the String rewriting of equals:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while(n-- ! =0) {
                if(v1[i] ! = v2[i])return false;
                i++;
            }
            return true; }}return false;
}
Copy the code

The String class calls equals to compare whether the contents of the String are equal. Such as:

public static void main(String[] args) {
    Integer a = 500;
    Integer b = 600;
    boolean result = a.equals(b);
    System.out.println(result);
}
Copy the code

Observe the implementation of the Integer class:

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}
Copy the code

It still compares values, but without overriding equals:

@AllArgsConstructor
static class User {
    private String name;
    private Integer age;
}

public static void main(String[] args) {
    User user = new User("zs".20);
    User user2 = new User("zs".20);
    boolean result = user.equals(user2);
    System.out.println(result);
}
Copy the code

Even if the values in two objects are the same, they are not equal because they perform the equals method of the Object class:

public boolean equals(Object obj) {
    return (this == obj);
}
Copy the code

Equals equals equals equals equals equals equals equals equals equals equals equals equals equals equals equals equals equals equals equals equals

@AllArgsConstructor
static class User {
    private String name;
    private Integer age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null|| getClass() ! = o.getClass())return false;
        User user = (User) o;
        returnObjects.equals(name, user.name) && Objects.equals(age, user.age); }}Copy the code

Let’s talk about the hashCode method, which is a native method that returns the hash value of an Object. Normally, we don’t use this method, except for the toString method of the Object class:

public String toString(a) {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
Copy the code

Why do you have to override hashCode whenever you override equals

Now that we know what these two methods do, let’s address the main point of this article. Why should we override hashCode whenever we override equals? This is for collections that use hashCode methods, such as HashMap, HashSet, etc.

public static void main(String[] args) {
    Map<Object, Object> map = new HashMap<>();
    String s1 = new String("key");
    String s2 = new String("key");

    map.put(s1, 1);
    map.put(s2, 2);
    map.forEach((k, v) -> {
        System.out.println(k + "--" + v);
    });
}
Copy the code

If a HashMap contains only one key, the value of the key will be overwritten. If a HashMap contains only one key, the value of the key will be overwritten.

@AllArgsConstructor
@ToString
static class User {
    private String name;
    private Integer age;
}

public static void main(String[] args) {
    Map<Object, Object> map = new HashMap<>();
    User user = new User("zs".20);
    User user2 = new User("zs".20);

    map.put(user, 1);
    map.put(user2, 2);
    map.forEach((k, v) -> {
        System.out.println(k + "--" + v);
    });
}
Copy the code

What should the result be? Is there only one piece of data in the HashMap as before? The result is this:

EqualsAndHashCodeTest.User(name=zs, age=20)--1
EqualsAndHashCodeTest.User(name=zs, age=20)--2
Copy the code

Why is that? This is because HashMap considers these two objects to be different, so we override equals:

@AllArgsConstructor
@ToString
static class User {
    private String name;
    private Integer age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null|| getClass() ! = o.getClass())return false;
        User user = (User) o;
        returnObjects.equals(name, user.name) && Objects.equals(age, user.age); }}public static void main(String[] args) {
    Map<Object, Object> map = new HashMap<>();
    User user = new User("zs".20);
    User user2 = new User("zs".20);

    System.out.println(user.equals(user2));

    map.put(user, 1);
    map.put(user2, 2);
    map.forEach((k, v) -> {
        System.out.println(k + "--" + v);
    });
}
Copy the code

Running results:

true
EqualsAndHashCodeTest.User(name=zs, age=20)--1
EqualsAndHashCodeTest.User(name=zs, age=20)--2
Copy the code

The two objects are judged to be the same, but the HashMap still stores two pieces of data, indicating that the HashMap still considers these two objects to be different. This actually involves the underlying principle of HashMap. Look at the put method of HashMap:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false.true);
}
Copy the code

Before storing data, the HashMap calls the hash method on the key:

static final int hash(Object key) {
    int h;
    return (key == null)?0 : (h = key.hashCode()) ^ (h >>> 16);
}
Copy the code

This method will call the hashCode method of the key and perform operations such as right shift, Xor, etc., to get the hash value of the key and use the hash value to calculate the insertion position of the data. If there is no element in the current position, it will be directly inserted, as shown in the following figure:

Since the two objects have different hash values, they get different insertion positions, resulting in the HashMap ending up storing two pieces of data.

Next we override the hashCode and equals methods of the User object:

@AllArgsConstructor
@ToString
static class User {
    private String name;
    private Integer age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null|| getClass() ! = o.getClass())return false;
        User user = (User) o;
        return Objects.equals(name, user.name) && Objects.equals(age, user.age);
    }

    @Override
    public int hashCode(a) {
        returnObjects.hash(name, age); }}Copy the code

The hash value for both objects will be the same:

When user2 hashes to the same insertion position, it will find that there is already data at the same position. Then it will fire equals and compare the contents of the two objects. If they are identical, they will be considered the same object and the new value will overwrite the old value. A HashMap always considers two new objects to be different because they cannot have the same address value.

Since the String class overrides the hashCode and equals methods, we can safely use String as the key of a HashMap.

In a HashSet, a similar problem occurs:

@AllArgsConstructor
@ToString
static class User {
    private String name;
    private Integer age;
}

public static void main(String[] args) {
    Set<Object> set = new HashSet<>();
    User user = new User("zs".20);
    User user2 = new User("zs".20);

    set.add(user);
    set.add(user2);

    set.forEach(System.out::println);
}
Copy the code

For two objects with the same content, if hashCode and equals methods are not overridden, the HashSet will not be considered duplicate, so both User objects will be stored.

conclusion

The essence of hashCode is to help HashMap and HashSet collections speed up the efficiency of insertion. When inserting a data, hashCode can quickly calculate the insertion position, eliminating the need to use equlas methods for comparison from beginning to end, but in order not to cause problems, we need to follow the following rules:

  • Two identical objects must have the same hashCode value
  • If two objects have the same hashCode value, they are not necessarily the same

So, if you don’t override the hashCode method, two identical objects will appear in the HashSet and two identical keys will appear in the Map, which is not allowed. To sum up, in everyday development, whenever you override the equals method, you must override hashCode.