equals() ? ==? hashCode()? Get to know you all today

Posted by amorphous on Mon, 07 Mar 2022 21:40:53 +0100

catalogue

  • Data types in Java
  • When to use the relational operator = =, and when to use the equals method?
  • For the equals method, why did you report a null pointer Java lang.NullPointerException?
  • What does the hashCode method do? What is the relationship between hashCode and equals?
  • Why must the hashCode method be overridden in every class that overrides the equals method?

data type

Data types in java can be divided into two categories:

1. Basic data type (original data type)

byte,short,char,int,long,float,double,boolean. The comparison between them applies the double equal sign (= =), and the basic data type compares their values.

2. Reference type (class, interface, array)

When they compare with (= =), they compare the storage address in memory, the object is placed in the heap, and the reference (address) of the object is stored in the stack. It can be seen that '= =' compares the address values in the stack when the object being compared is a reference type.

Relational operator==

The relational operators contained in java include less than (<), greater than (>), less than or equal to (< =), greater than or equal to (> =), equal to (= =), and not equal to (! =).

==And= Applies to all objects, but these two operators usually have problems when comparing objects:

Here = = and= The comparison is the reference of the object. Although the contents of objects are the same, the references of objects are different. n1==n2 is false.

        Integer n1 = new Integer(47);
        Integer n2 = new Integer(47);
        
        System.out.println(n1 == n2);  //false
        System.out.println(n1 != n2);  //true

Here = = compares the basic data type, so it will compare whether the values are equal So n1 == n2 outputs true

        int n1 = 100;
        int n2 = 100;

        System.out.println(n1 == n2);  //true
        System.out.println(n1 != n2);  //false

equals method

By default, the equals method of an Object calls the equals method of the Object class The source code is as follows:

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

Note that this is equivalent to = =, where the reference object is compared, so the address is compared (whether it is the same object or not)

In the second case, the equals method of the object is overridden For example, a String object The source code is as follows:

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;//If it is the same object, return directly
        }
        if (anObject instanceof String) {//It is the String object that starts judging the content
            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) {//Compare characters one by one. If there are unequal characters, return false
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

At this time, the rewriting implementation of equals method is different, but after rewriting, whether the objects are equal is generally judged by whether the contents of the objects are equal. For most Java class libraries, equals() method is implemented to compare the contents of the objects rather than the references of the objects

Avoid the equals method and report null pointer

To avoid null pointers in the equals method, let's first tell you that the answer is to use Objects Equals (a, b) adds an Objects tool class in JDK7. It provides some methods to operate Objects. It consists of some static practical methods. These methods are null save (null pointer safe) or null tolerance (null pointer tolerant), which are used to calculate the hashcode of the object, return the string representation of the object, and compare the two Objects.

By default, the equals method of the Object is not overridden, and the equals method in the Object class is called

Then let's write an example of error reporting:

        A a = null;//Suppose I receive a config object, and I don't know if it is empty, I will compare it
        boolean r = a.equals(new B());
        System.out.println(r); //Output Java lang.NullPointerException

At this time, due to our negligence, we did not verify the parameters after receiving the parameters, resulting in calling the equals method to report a null pointer

//Other examples are:
null.equals("java Baodian");  //NullPointerException

"java Baodian".equals(null);  //false the result is only available if the object to the left of equals is not Null

null.equals(null);  //NullPointerException

Using objects Equals (a, b), both left and right sides are Null, and Null pointers will not be reported

 Objects.equals(null,"java Baodian");  //false
 
 Objects.equals("java Baodian",null);  //false
 
 Objects.equals(null,null);  //true

Take a look at objects The source code of the equals method, which tolerates null pointers

    public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }

hashCode() method

Hash is actually a person's name. Because he proposed the concept of a hash algorithm, it was named after him. Hash algorithm, also known as hash algorithm, refers to the direct assignment of data to an address according to a specific algorithm. Popular understanding is a method to create a small digital "fingerprint" from any kind of data.

In java, by default, objects do not override the hashCode() method Use the in the Object class

  public native int hashCode(); //It is a native method

The hashCode method defined by the Object class will return different integers for different objects. (this is achieved by converting the internal address of the Object to an integer)

example:

        Config config1 = new Config();
        Config config2 = new Config();

        System.out.println(config1.hashCode());  //1128032093

        System.out.println(config2.hashCode());  //1066516207

        System.out.println(config1.equals(config2));  //false

Relationship between hashCode and equals

Both are methods in the Object class. Since the Object class is the base class of all classes, these two methods can be overridden in all classes.

  • Principle 1: if x.equals(y) returns "true", the hashCode() of X and y must be equal;

  • Principle 2: if x.equals(y) returns "false", the hashcodes () of X and Y may be equal or unequal;

  • Principle 3: if the hashCode() of X and y are not equal, x.equals(y) must return "false";

  • Principle 4: Generally speaking, the equals method is called by the user, while the hashcode method is not called by the user;

  • Principle 5: when an object type is used as an element of a collection object, the object should have its own equal () and hashCode() designs, and abide by the above principles.

During the execution of a Java application, when the hashCode method is called multiple times on the same object, the same integer must be returned consistently, provided that the information used to compare the objects with equals has not been modified. This integer does not need to be consistent from one execution of an application to another execution of the same application.

If two objects are equal according to the equals(Object) method, calling the hashCode method on each of the two objects must produce the same integer result.

If the two objects are not equal according to the equals(java.lang.Object) method, calling the hashCode method on any of the two objects does not necessarily require different integer results. However, programmers should be aware that generating different integer results for unequal objects can improve the performance of hash tables.

Why must the hashCode method be overridden in every class that overrides the equals method?

In each class that overrides the equals method, the hashCode method must also be overridden. If you do not do so, you will violate object hashCode, which makes it impossible for this class to work together with all hash based collections

Above, we introduced what hashCode is. To further understand the application of hashCode, we must first understand the container in Java, because hashCode is only useful in data structures that need hash algorithm, such as HashSet and HashMap

Take hashMap as an example:

HashMap is a data storage structure composed of arrays and linked lists. To determine where a data is stored in the array is to calculate the location through the hashCode method. If there is a conflict, the equals method will be called for comparison. If it is different, it will be added to the tail of the linked list. If it is the same, the original data will be replaced. Of course, the calculation position is not calculated by the simple hashCode method above. There are some other steps in the process. Here we can simply think that the hashCode determines the position. The code is as follows:

public V put(K key, V value) {
	    // If the hash table is not initialized, it will be initialized
	    if (table == EMPTY_TABLE) {
	        // Initialize hash table
	        inflateTable(threshold);
	    }
	 
	    // When the key is null, call the putForNullKey method and save the null in the first position of the table, which is the reason why HashMap allows nulls
	    if (key == null) {
	        return putForNullKey(value);
	    }
	 
	    // Calculate the hash value of the key
	    int hash = hash(key);
	    // Locate the specified slot of the entry array according to the hash value of the key and the length of the array
	    int i = indexFor(hash, table.length);
	    // Get the entry on the storage location. If the entry is not empty, traverse the linked list where the entry is located
	    for (Entry<K, V> e = table[i]; e != null; e = e.next) {
	        Object k;
	        // Judge whether the key exists through the hashCode and equals methods of the key. If so, replace the old value with the new value and return the old value
	        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
	            V oldValue = e.value;
	            e.value = value;
	            e.recordAccess(this);
	            return oldValue;
	        }
	    }
	 
	    // Number of modifications increased by 1
	    modCount++;
	    // If the linked list cannot be found or the key does not exist after traversing the linked list, create a new Entry and add it to the HashMap
	    addEntry(hash, key, value, i);
	    return null;
	} 

In the above method, a method is called to see that the subscript of an array is determined according to the return value of the incoming element hashCode method and the specific value XOR.

static int indexFor(int h, int length) {
        // The hash value and length-1 are summed to calculate the index
        return h & (length - 1);
    }

Back to our question: why does every class that overrides the equals method also have to override the hashCode method?

If you override equals and the implementation of hashcode is not rewritten, the hashcode method of the class is the default hashcode method of the Object. Since the default hashcode method is a value obtained by the hash algorithm according to the memory address of the Object, it is likely that some two objects are "equal" but the hashcode is different.

In this way, when you save one of them as a key to HashMap, hasoTable or HashSet, and then find the other as the key value with "equal", you can't find them at all. As a result, HashSet and HashMap cannot operate normally

For example, a class a rewrites the equals method but does not rewrite the hashcode method. Objects a1 and a2 use the equals method. According to the usage of hashcode above, their hashcodes must be equal. However, since the hashcode method is not rewritten here, their two hashcodes are different. Therefore, after rewriting the equals method, The hashcode method is also rewritten as much as possible. Through a certain algorithm, they will have the same hashcode value when equals is equal.

summary

  • ==When comparing basic data types, values are compared

  • ==When comparing reference data types, the reference address of the object is compared

  • The equals method of the object, without rewriting, uses = =, and compares the reference address of the object

  • The equals method of the object is used to compare whether the contents of the object are equal after rewriting. The implementation can be generated by IDE or customized (for example, the override of the equals method by the String class is to compare characters one by one)

  • Without rewriting, the equals method of the Object calls the equals method in the Object class. When the left side of the condition is Null, it will report a Null pointer and use objects Equals (a, b) avoids Null pointers

  • hashcode is used by the system to quickly retrieve objects

  • After rewriting the equals method, you should also rewrite the hashcode method, otherwise HashSet, HashMap and other containers that depend on hashcode will not work normally

Pay attention to the official account: java treasure

Topics: Java