Understanding the Relationship Between hashCode() and equals() in Java

Understanding the Relationship Between hashCode() and equals() in Java
The growth path of a programmer
Internet/Programmer/Technology/Data Sharing
Follow

It takes about 6 minutes to read this article.

From: Selected Java Interview Questions

The previous article mentioned that the equals() and hashCode() methods might lead to the interview question about “the relationship between hashCode() and equals()?” This article will analyze this basic interview question.
First, let’s look at a diagram and think about why?
Understanding the Relationship Between hashCode() and equals() in Java

Introduction

The equals() method is used to determine whether two objects are equal.
The hashCode() method is used to obtain the hash code, also known as the hash value; it actually returns an int integer. The purpose of this hash code is to determine the index position of the object in the hash table.

Relationship

We will explain the relationship between hashCode() and equals() in two scenarios based on the “purpose of the class”.

1. No “hash table for the class” is created

What is meant by “no hash table for the class” is that we do not use this class in data structures such as HashSet, Hashtable, HashMap, etc., which are essentially hash tables. For example, we will not create a HashSet collection of this class.
In this case, the class’s “hashCode() and equals()” have no relation at all! The equals() method is used to compare whether two objects of this class are equal. The hashCode() method is completely irrelevant.
Next, we will look at an example to see the hashCode() values when the two objects of the class are equal and not equal.
import java.util.*;
import java.lang.Comparable;

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

    public static void main(String[] args) {
        // Create 2 Person objects with the same content,
        // and compare them using equals to see if they are equal
        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;  
            }  

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

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

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

From the results, we can also see that when p1 and p2 are equal, hashCode() may not necessarily be equal.

2. A “hash table for the class” is created

What is meant by “a hash table for the class is created” is that we will use this class in data structures such as HashSet, Hashtable, HashMap, etc., which are essentially hash tables. For example, we will create a HashSet collection of this class.
In this case, the class’s “hashCode() and equals()” are related:
  • If two objects are equal, then their hashCode() values must be the same. Here, equality means that when comparing two objects using equals(), it returns true.

  • If the hashCode() values of two objects are equal, they are not necessarily equal. Because in a hash table, equal hashCode() values mean that the hash values of two key-value pairs are equal. However, equal hash values do not necessarily imply equal key-value pairs. To add,: “Two different key-value pairs can have the same hash value,” which is known as a hash collision.

Additionally, in this case, to determine whether two objects are equal, you must override both the equals() and hashCode() functions. Otherwise, equals() will be ineffective.
For example, when creating a HashSet collection of the Person class, you must override both the equals() and hashCode() methods of the Person class.
If you only override the equals() method, you will find that the equals() method does not achieve the desired effect.
import java.util.*;
import java.lang.Comparable;

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

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

        // Create a 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;  
            }  

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

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

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

Result Analysis:
We have overridden the equals() of the Person class. However, it is strange to find that the HashSet still contains duplicate elements: p1 and p2. Why does this happen?
This is because although p1 and p2 have equal contents, their hashCode() is not equal; therefore, the HashSet considers them not equal when adding p1 and p2.
What about overriding both the equals() and hashCode() methods?
import java.util.*;
import java.lang.Comparable;

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

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

        // Create a 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;  
            }  

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

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

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

Result Analysis:
Now, the equals() works, and there are no duplicate elements in the HashSet.
When comparing p1 and p2, we find that their hashCode() is equal, and comparing them using equals() also returns true. Therefore, p1 and p2 are considered equal.
When comparing p1 and p4, we find that although their hashCode() is equal, comparing them using equals() returns false. Therefore, p1 and p4 are considered not equal.

Principles

1. The same object (without modification) must always return the same value when calling hashCode(). If a key object calls hashCode() to determine its storage location when put, and calls hashCode() again when getting, obtaining a different return value means that it maps to a different location, so the original key-value pair cannot be found.
2. Objects with equal hashCode() values are not necessarily equal; through hashCode() and equals(), an object must be uniquely determined. Non-equal objects can have equal hashCode() results. When considering collision issues with hashCode(), speed of generation should also be taken into account; perfect hash is unrealistic.
3. Once the equals() function is overridden (when overriding equals, pay attention to satisfy reflexivity, symmetry, transitivity, and consistency), the hashCode() function must also be overridden. The generated hash value of hashCode() should be based on the fields used in equals() to compare equality.
If two objects defined as equal by equals() generate different hashCode() values, for hashMap, they may be mapped to different locations without the chance to call equals() to check equality, leading to errors where two actually equal objects may be inserted in different locations. Other hash-based collection classes may also have this issue.
<END>

Recommended Reading:

The graduation project has a landing! An open-source license plate recognition system based on SpringBoot

The incident of the overselling of Feitian Maotai: Please use Redis distributed locks with caution!

5T technology resources are being released! Including but not limited to: C/C++, Linux, Python, Java, PHP, Artificial Intelligence, Microcontrollers, Raspberry Pi, etc. Reply “2048” in the public account to get it for free!!

Understanding the Relationship Between hashCode() and equals() in Java

Scan the QR code to follow my public account

Leave a message

I have read Understanding the Relationship Between hashCode() and equals() in Java

Leave a Comment

×