The equals and hashCode methods in Java are in the Object, so each Object has these two methods. Sometimes we need to implement specific requirements, and we may need to rewrite these two methods, today we will introduce the functions of these two methods.
The equals () and hashCode () methods are used for comparison in the same class, especially when a set in a container stores the same class object to determine whether the Put object is repeated.
First, we need to understand the following question:
Equals () equals two objects, hashcode () must be equal, equals () is not equal to two objects, but it cannot prove that their hashcode () is not equal. In other words, hashCode () may be equivalent to two objects whose equals () method is not equal. (In my understanding, the hash code is generated due to a conflict)
Here, hashCode is like the index of each word in the dictionary. equals () is like comparing different words under the same word in the dictionary. It is like finding the two words "yourself" and "spontaneous" under the word "self" in the dictionary. If equals () is used to judge that the query words are equal, it is the same word, for example, if both the words equals () are compared are "self", then the value obtained by the hashCode () method must be equal. If equals () is used () the method compares the words "self" and "spontaneous", so you don't want to wait for the result, however, these two words belong to the same word under the word "self", that is, the hashCode () is the same. If equals () is used to compare the words "yourself" and "they", the results are different, and hashCode () is also different.
In turn, hashcode () is not equal. equals () is always available. hashcode () is equal. equals () may be equal or not. In the object class, the hashcode () method is a local method and returns the address value of the object. The equals () method in the object class compares the address values of the two objects, if equals () is equal, the address values of the two objects are equal, and hashcode () is equal;
Meanwhile, the hash algorithm provides high efficiency for searching elements.
If you want to find whether an object exists in a collection, how do you write the approximate program code?
You Usually retrieve each element one by one and compare it with the object to be searched. When you find that an element and the object to be searched have the same equals method comparison result, stop searching and return positive information. Otherwise, return negative information. If a set contains many elements, such as 10 thousand elements, and does not contain the object to be searched, this means that your program needs to extract 10 thousand elements from the set and compare them one by one to draw a conclusion.
Someone has invented a hash algorithm to improve the efficiency of searching elements from the set. In this way, the set is divided into several storage areas, and each object can calculate a hash code, you can group hash codes (Calculated using different hash functions). Each group corresponds to a storage region, based on the hash of an object, can we determine the region in which the object should be stored? HashSet refers to the set of objects accessed using the hash algorithm, it groups hash codes and divides the storage areas of objects by using the remainder of a certain number n (this hash function is the simplest; the Object class defines a hashCode () method to return the hash code of each Java Object. When you look for an Object from the HashSet collection,The Java System first calls the hashCode () method of the object to obtain the hash table of the object, and then finds the corresponding storage area based on the hash, finally, obtain each element in the storage area and compare it with the equals method of the object.In this way, we can draw a conclusion without traversing all the elements in the set. It can be seen that the HashSet set has good object retrieval performance, but the efficiency of storing objects in the HashSet set is relatively low, because when an object is added to a HashSet, calculate the hash code of the object and determine the storage location of the object in the Set Based on the hash code. In order to ensure that the instance object of a class can be properly stored in the HashSet, the two instance objects of this class must have the same hash code when they are compared using the equals () method. That is to say, ifObj1.equals (obj2)If the result is true, the result of the following expression must also be true:
Obj1.hashCode () = obj2.hashCode ()
In other words, when we override the equals method of an object, we must override its hashCode method, but not its hashCode method, the hashCode method in an Object always returns the hash address of an Object, which is never equal. So at this time, even if the equals method is rewritten, it will not have a specific effect, because if the hashCode method does not want to wait, it will not call the equals Method for comparison, so it is meaningless.
If the hashCode () method of a class does not comply with the preceding requirements, when the two instance objects of the class compare with the equals () method, they should not be stored in the set at the same time, but if they are stored in the HashSet set, because of their hashCode () the Return Value of the method is different (the return value of the hashCode method in the Object is always different). The second Object may be put into a region different from the first Object based on the hash code calculation. In this way, it cannot be compared with the equals method of the first Object, and it may be stored in the HashSet collection. The hashCode () method in the Object class cannot meet the requirements that the Object is stored in the HashSet, because the return value is calculated by the object's memory address, the hash value returned by the same object at any time during the running of the program remains unchanged. Therefore, as long as they are two different instance objects, even if their equals Method Comparison results are equal, their default hashCode method returns different values.
Here is a specific example:
RectObject object:
package com.weijia.demo;public class RectObject {public int x;public int y;public RectObject(int x,int y){this.x = x;this.y = y;}@Overridepublic int hashCode(){final int prime = 31;int result = 1;result = prime * result + x;result = prime * result + y;return result;}@Overridepublic boolean equals(Object obj){if(this == obj)return true;if(obj == null)return false;if(getClass() != obj.getClass())return false;final RectObject other = (RectObject)obj;if(x != other.x){return false;}if(y != other.y){return false;}return true;}}
We have rewritten the hashCode and equals methods in the parent class Object. We can see that in the hashCode and equals methods, if the x and y values of the two RectObject objects are equal, their hashCode values are equal, at the same time, equals returns true;
The following is the test code:
package com.weijia.demo;import java.util.HashSet;public class Demo {public static void main(String[] args){HashSet
set = new HashSet
();RectObject r1 = new RectObject(3,3);RectObject r2 = new RectObject(5,5);RectObject r3 = new RectObject(3,3);set.add(r1);set.add(r2);set.add(r3);set.add(r1);System.out.println("size:"+set.size());}}
We saved four objects to the HashSet and printed the size of the set. What is the result?
Running result: size: 2
Why is it 2? This is simple, because we have rewritten the hashCode method of the RectObject class. As long as the x and y values of the RectObject object are equal, their hashCode values are equal, so we should first compare the hashCode values, the x and y attribute values of r1 and r2 objects are different, so their hashCode is different. Therefore, r2 objects can be put in, but the x, the value of y is the same as that of the r1 object. Therefore, hashCode is equivalent. In this case, the equals method of r1 and r3 is compared, because the values of x and y are equal, therefore, r1 and r3 objects are equal, so r3 objects cannot be put in. Similarly, an r1 object is not added at the end. Therefore, there is only one r1 and r2 object in the set object.
Next we will comment out the hashCode method in the RectObject Object, that is, do not overwrite the hashCode method in the Object, and run the Code:
Running result: size: 3
This result is also very simple. First, judge the hashCode of the r1 Object and the r2 Object, because the hashCode method in the Object returns the Conversion Result of the local memory address of the Object, the hashCode of different instance objects is different. Similarly, because the hashCode of r3 and r1 is not equal, but r1 = r1, only r1, r2, r3, so the size is 3
Next we will comment out the content in the equals method in the RectObject object, directly return false, without commenting on the hashCode method, and run the Code:
Running result: size: 3
This result is a bit unexpected. Let's analyze it:
First, the objects of r1 and r2 are compared with hashCode, which is not equal. Therefore, r2 is put into set. Let's take a look at r3 and compare the hashCode methods of r1 and r3, which are equal, then compare the two equals methods. Since the equals method always returns false, r1 and r3 are not equal. r3 and r2 do not need to be said. Their hashcodes are not equal, so r3 is put into the set, and then look at r4. Comparing r1 and r4, we find that the hashCode is equal. In the equals method, because equals returns false, r1 and r4 are not equal, the same r2 and r4 are not equal, and r3 and r4 are not equal. Therefore, r4 can be placed in the set, and the result should be size: 4. Why is it 3?
At this time, we need to check the source code of HashSet. below is the source code of the add method in HashSet:
/** * Adds the specified element to this set if it is not already present. * More formally, adds the specified element e to this set if * this set contains no element e2 such that * (e==null ? e2==null : e.equals(e2)). * If this set already contains the element, the call leaves the set * unchanged and returns false. * * @param e element to be added to this set * @return true if this set did not already contain the specified * element */ public boolean add(E e) { return map.put(e, PRESENT)==null; }
Here we can see that HashSet is implemented based on HashMap. We click the put Method of HashMap. The source code is as follows:
/** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with key, or * null if there was no mapping for key. * (A null return can also indicate that the map * previously associated null with key.) */ public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry
e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
Let's take a look at the judgment conditions of if,
The first is to judge whether the hashCode is equal. If not, skip directly. If the hashCode is equal, then compare whether the two objects are equal or the equals method of the two objects, because it is an operation or operation, so as long as there is one, we can explain it here. In fact, the size of the set above is 3, because the last r1 is not put in, we thought that r1 = r1 returned true, so we didn't put it in. Therefore, the set size is 3. If we set the hashCode method to always return false, This set is 4.
Finally, let's take a look at the memory leakage caused by hashCode: Let's take a look at the Code:
Package com. weijia. demo; import java. util. HashSet; public class Demo {public static void main (String [] args) {HashSet
Set = new HashSet
(); RectObject r1 = new RectObject (3, 3); RectObject r2 = new RectObject (5, 5); RectObject r3 = new RectObject (3, 3); set. add (r1); set. add (r2); set. add (r3); r3.y = 7; System. out. println ("size before deletion:" + set. size (); set. remove (r3); System. out. println ("size after deletion:" + set. size ());}}
Running result:
Size before deletion: 3
Size after deletion: 3
We found a problem and it was a big problem. We called remove to delete the r3 object, so we thought we deleted r3, but in fact it was not deleted. This is called Memory leakage, the object is not used, but it is still in the memory. Therefore, after we perform this operation multiple times, the memory will burst. Let's take a look at the source code of remove:
/** * Removes the specified element from this set if it is present. * More formally, removes an element e such that * (o==null ? e==null : o.equals(e)), * if this set contains such an element. Returns true if * this set contained the element (or equivalently, if this set * changed as a result of the call). (This set will not contain the * element once the call returns.) * * @param o object to be removed from this set, if present * @return true if the set contained the specified element */ public boolean remove(Object o) { return map.remove(o)==PRESENT; }
Then let's look at the source code of the remove Method:
/** * Removes the mapping for the specified key from this map if present. * * @param key key whose mapping is to be removed from the map * @return the previous value associated with key, or * null if there was no mapping for key. * (A null return can also indicate that the map * previously associated null with key.) */ public V remove(Object key) { Entry
e = removeEntryForKey(key); return (e == null ? null : e.value); }
Let's take a look at the source code of the removeEntryForKey method:
/** * Removes and returns the entry associated with the specified key * in the HashMap. Returns null if the HashMap contains no mapping * for this key. */ final Entry
removeEntryForKey(Object key) { int hash = (key == null) ? 0 : hash(key); int i = indexFor(hash, table.length); Entry
prev = table[i]; Entry
e = prev; while (e != null) { Entry
next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
We can see that when the remove method is called, The hashCode value of the object is used to locate the object and then delete it, this problem is because we modified the value of the y attribute of the r3 object, and because the hashCode of the RectObject object involves the y value in the calculation, the hashCode of the r3 object is changed, so r3 is not found in the remove method, so deletion fails. That is to say, the hashCode of r3 has changed, but its storage location is not updated and it is still in the original location. Therefore, when we use its new hashCode to find it, we cannot find it.
In fact, the implementation of the above method is very simple: for example:
A simple linear hash table uses the mod hash function. The source code is as follows:
/** * Returns index for hash code h. */ static int indexFor(int h, int length) { return h & (length-1); }
This is actually a mod operation, but this operation is more efficient than the % operation.
1, 2, 3, 4, 5 indicates the result of mod. Each element corresponds to a linked list structure, so you want to delete an Entry First, obtain the hashCode to get the head node of the linked list, and then traverse the linked list. If the hashCode and equals are equal, delete the element.
The memory leakage above tells me a piece of information: If we participate in the hashCode operation of the object's attribute values, we cannot modify the attribute values when deleting them, otherwise, serious problems may occur.
In fact, we can also take a look at the object type corresponding to the eight basic data types, the hashCode method of the String type, and the equals method.
In 8, the basic hashCode type is very simple, that is, the size of their values is directly returned. The String object is calculated in a complex way, but this calculation method can ensure that, if the value of this string is equal, their hashCode is equal. The equals method of the eight basic types directly compares the values. The equals method of the String type compares the values of the strings.