Interview frequency: what is the relationship between hashCode() and equals()?

First offer a picture, you can think about why?

introduce

equals() is used to determine whether two objects are equal.

hashCode() is used to obtain hash code, also known as hash code; It actually returns an int integer. This hash code is used to determine the index position of the object in the hash table.

relationship

We use "class purpose" to explain the "relationship between hashCode() and equals()" in two cases.

1. Hash table for class is not created

The phrase "do not create hash table corresponding to class" here means that we will not use this class in HashSet, Hashtable, HashMap and other data structures that are essentially hash tables. For example, the HashSet collection for this class is not created.

In this case, "hashCode() and equals()" of this class have no relationship of half a dime! Equals () is used to compare whether two objects of this class are equal. Hashcode () has no effect at all.

Next, we use an example to see the value of hashCode() when two objects of a class are equal and unequal.

import java.util.*;
import java.lang.Comparable;

/**
 * @desc Compare the value of hashCode() when equals() returns true and false.
 *
 */
public class NormalHashCodeTest{

    public static void main(String[] args) {
        // Create two Person objects with the same content,
        // Then compare them with equals
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        Person p3 = new Person("aaa", 200);
        System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
        System.out.printf("p1.equals(p3) : %s; p1(%d) p3(%d)\n", p1.equals(p3), p1.hashCode(), p3.hashCode());
    }

    /**
     * @desc Person Class.
     */
    private static class Person {
        int age;
        String name;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String toString() {
            return name + " - " +age;
        }

        /** 
         * @desc Override equals method 
         */  
        public boolean equals(Object obj){  
            if(obj == null){  
                return false;  
            }  

            //If it is the same object, return true; otherwise, return false  
            if(this == obj){  
                return true;  
            }  

            //Determine whether the types are the same  
            if(this.getClass() != obj.getClass()){  
                return false;  
            }  

            Person person = (Person)obj;  
            return name.equals(person.name) && age==person.age;  
        } 
    }
}

Operation results:

p1.equals(p2) : true; p1(1169863946) p2(1901116749)
p1.equals(p3) : false; p1(1169863946) p3(2131949076)

It can also be seen from the results that when p1 and p2 are equal, hashCode() is not necessarily equal.

2. A hash table corresponding to the class is created

The "hash table corresponding to the class" mentioned here means that we will use this class in HashSet, Hashtable, HashMap and other data structures that are essentially hash tables. For example, a HashSet collection of this class is created.

In this case, "hashCode() and equals()" of this class are related:

  • If two objects are equal, they must have the same hashCode() value. Equality here means that it returns true when comparing two objects through equals().

  • If two objects hashCode() are equal, they are not necessarily equal. Because in the hash table, hashCode() is equal, that is, the hash values of two key value pairs are equal. However, equal hash values do not necessarily lead to equal key value pairs. Add: "two different key value pairs have equal hash values", which is hash conflict.

In addition, in this case. To determine whether two objects are equal, override the hashCode() function in addition to equals (). Otherwise, equals() is invalid.

For example, to create a HashSet collection of a Person class, you must override both the equals() and hashCode() methods of the Person class.

If you just override the equals() method. We will find that the equals() method does not achieve the desired effect.

import java.util.*;
import java.lang.Comparable;

/**
 * @desc Compare the value of hashCode() when equals() returns true and false.
 *
 */
public class ConflictHashCodeTest1{

    public static void main(String[] args) {
        // Create a new Person object,
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        Person p3 = new Person("aaa", 200);

        // New HashSet object 
        HashSet set = new HashSet();
        set.add(p1);
        set.add(p2);
        set.add(p3);

        // Compare p1 and p2 and print their hashCode()
        System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
        // Print set
        System.out.printf("set:%s\n", set);
    }

    /**
     * @desc Person Class.
     */
    private static class Person {
        int age;
        String name;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String toString() {
            return "("+name + ", " +age+")";
        }

        /** 
         * @desc Override equals method 
         */  
        @Override
        public boolean equals(Object obj){  
            if(obj == null){  
                return false;  
            }  

            //If it is the same object, return true; otherwise, return false  
            if(this == obj){  
                return true;  
            }  

            //Determine whether the types are the same  
            if(this.getClass() != obj.getClass()){  
                return false;  
            }  

            Person person = (Person)obj;  
            return name.equals(person.name) && age==person.age;  
        } 
    }
}

Operation results:

p1.equals(p2) : true; p1(1169863946) p2(1690552137)
set:[(eee, 100), (eee, 100), (aaa, 200)]

Result analysis:

We have rewritten the equals() of Person. However, it is strange to find that there are still duplicate elements in the HashSet: p1 and p2. Why does this happen?

This is because although the contents of p1 and p2 are equal, their hashcodes () are different; Therefore, when adding p1 and p2, HashSet thinks they are not equal.

What about overriding the equals() and hashCode() methods at the same time?

import java.util.*;
import java.lang.Comparable;

/**
 * @desc Compare the value of hashCode() when equals() returns true and false.
 *
 */
public class ConflictHashCodeTest2{

    public static void main(String[] args) {
        // Create a new Person object,
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        Person p3 = new Person("aaa", 200);
        Person p4 = new Person("EEE", 100);

        // New HashSet object 
        HashSet set = new HashSet();
        set.add(p1);
        set.add(p2);
        set.add(p3);

        // Compare p1 and p2 and print their hashCode()
        System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
        // Compare p1 and p4 and print their hashCode()
        System.out.printf("p1.equals(p4) : %s; p1(%d) p4(%d)\n", p1.equals(p4), p1.hashCode(), p4.hashCode());
        // Print set
        System.out.printf("set:%s\n", set);
    }

    /**
     * @desc Person Class.
     */
    private static class Person {
        int age;
        String name;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String toString() {
            return name + " - " +age;
        }

        /** 
         * @desc Override hashCode 
         */  
        @Override
        public int hashCode(){  
            int nameHash =  name.toUpperCase().hashCode();
            return nameHash ^ age;
        }

        /** 
         * @desc Override equals method 
         */  
        @Override
        public boolean equals(Object obj){  
            if(obj == null){  
                return false;  
            }  

            //If it is the same object, return true; otherwise, return false  
            if(this == obj){  
                return true;  
            }  

            //Determine whether the types are the same  
            if(this.getClass() != obj.getClass()){  
                return false;  
            }  

            Person person = (Person)obj;  
            return name.equals(person.name) && age==person.age;  
        } 
    }
}

Operation results:

p1.equals(p2) : true; p1(68545) p2(68545)
p1.equals(p4) : false; p1(68545) p4(68545)
set:[aaa - 200, eee - 100]

Result analysis:

Now, equals() takes effect, and there are no duplicate elements in the HashSet.

Comparing p1 and p2, we find that their hashcodes () are equal, and they also return true through equals(). Therefore, p1 and p2 are considered equal.

Comparing p1 and p4, we find that although their hashcodes () are equal; However, by comparing them with equals(), they return false. Therefore, p1 and p4 are considered to be unequal.

principle

1. Whenever the same object (not modified) calls hashCode(), the return value must be the same.
If a key object calls hashCode() during put to determine the storage location, and calls hashCode() during get to get a different return value. This value maps to a different place from the original, then the original key value pair must not be found.

2. Objects with equal return values of hashCode() are not necessarily equal. An object must be uniquely identified through hashCode() and equals(). The results of hashCode() of unequal objects can be equal. hashCode() should also pay attention to the generation speed when paying attention to collision. Perfect hash is unrealistic.

3. Once the equals() function is rewritten (reflexivity, symmetry, transitivity and consistency must be satisfied when rewriting equals), the hashCode() function must be rewritten. Moreover, the hash value generated by hashCode() should be based on the field used to compare equality in equals().

If the hashcodes generated by two equal objects specified by equals() are different, they are likely to be mapped to different locations for hashMap. There is no chance to call equals() to compare whether they are equal. Two actually equal objects may be inserted into different locations and errors occur. Other collection classes based on Hash methods may also have this problem

Keywords: Programming data structure Interview Programmer

Added by vronsky on Fri, 31 Dec 2021 21:30:51 +0200