"Java Combat" source code parsing why overwrite equals method always overwrite Hashcode method

Source: Internet
Author: User

1. Background knowledge

This code is based on jdk1.8 analysis, the Java programming idea has the following description:

Another look at Object.java's description of the Hashcode () method:

/** * Returns A hash code value for the object.     This method was * supported for the benefit of hash tables such as those provided by * {@link java.util.HashMap}.  * <p> * The general contract of {@code hashcode} are: * <ul> * <li>whenever It is invoked     On the same object more than once during * an execution of a Java application, the {@code Hashcode} method *  Must consistently return the same integer, provided no information * used in {@code equals} comparisons on the     Object is modified. * This integer need not remain consistent from one execution of a * application to another execution of the S     AME application. * <li>if Objects is equal according to the {@code equals (Object)} * method and then calling the {@code have     Hcode} method on each of * the objects must produce the same integer result. * <li>it is <em>not</em> required that if the objects is UNEqual * According to the {@link java.lang.object#equals (java.lang.Object)} * method, then calling the {@co  De hashcode} method on each of the * objects must produce distinct integer results. However, the * programmer should be aware this producing distinct integer results * for unequal objects Ma     Y improve the performance of hash tables. * </ul> * <p> * As much as is reasonably practical, the Hashcode method defined by * class {@code Object} does return distinct integers for distinct * objects. (This was typically implemented by converting the internal * address of the object to an integer, but this Implementa tion * technique isn't required by the * Java?     Programming language.)     * * @return A hash code value for this object. * @see java.lang.object#equals (java.lang.Object) * @see Java.lang.system#identityhashcode */public NAT ive int hashcode ();

For the 3-point Convention, translate as follows:

1) During Java application execution, if the information used for the comparison operation of the object's Equals method is not modified, then multiple hashcode methods must be consistently identical to the same integer for the same object. During multiple executions of the same application, the integer returned by each execution of the method can be inconsistent.

2) If two objects are equal according to the Equals (object) method, the Hashcode method that calls either object in both objects must produce the same integer result.

3) If two objects are not equal according to the Equals (object) method, then it is not necessary to call the Hashcode method of any of these two objects to produce different integer results. But the program ape should know that it is possible to improve the hash table performance by producing distinct integer results for different objects.


Therefore, overriding equals always overwrites hashcode as a common convention, not a necessity, if it works with a hash-based collection (HashMap, HashSet, HashTable), especially if the object is a key value. Be sure to overwrite the hashcode, otherwise an error will occur. So since it is a norm, it is necessary for us, as a procedural ape, to be executed to avoid problems.

The following is a case study of the necessity of HashMap

2, HashMap internal implementation

Common forms are as follows:

public class PhoneNumber {private int areacode;    private int prefix;    private int linenumber;        Public PhoneNumber (int areacode, int prefix, int linenumber) {this.areacode = AreaCode;        This.prefix = prefix;    This.linenumber = linenumber;        } @Override public boolean equals (Object o) {if (this = = O) return true;        if (o = = NULL | | getclass ()! = O.getclass ()) return false;        PhoneNumber that = (PhoneNumber) o;        if (areacode! = That.areacode) return false;        if (prefix! = That.prefix) return false;    return linenumber = = That.linenumber;        } @Override public int hashcode () {int result = AreaCode;        result = * result + prefix;        result = * result + linenumber;    return result; } public static void Main (string[] args) {map<phonenumber,string> phonenumberstringmap = new hashmap<ph  Onenumber,string> (); 1) Initialize Phonenumberstringmap.put (new PhoneNumber (123, 456, 7890), "Honghailiang");     2) put storage System.out.println (Phonenumberstringmap.get (New PhoneNumber (123, 456, 7890)); 3) Get Get}}
1) Initialization
/**     * Constructs an empty <tt>HashMap</tt> with the default initial capacity     * (+) and the default Lo Ad factor (0.75).     */Public    HashMap () {        this.loadfactor = default_load_factor;//All and fields defaulted    }

Create a hashmap with a default load factor, the default load factor is 0.75

2) put storage

/**     * 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 are to being associated     * @param value value to being associated with T He specified key     * @return The previous value associated with <tt>key</tt>, or     *         <tt>null& Lt;/tt> If there is no mapping for <tt>key</tt>.     *         (A <tt>null</tt> return can also indicate that the map     *         previously associated <tt> Null</tt> with <tt>key</tt>.)     */Public    V put (K key, V value) {        return Putval (hash (key), key, value, false, True);    }

As you can see from the note, the key value is the same, the former will be overwritten, that is, the HashMap does not allow duplicate key values. And the method has a return value, returns the previous value of the key value, and returns NULL if no map was previously available. Keep watching Putval.

/** * Implements Map.put and Related methods * * @param hash hash for key * @param key The key * @para M value the value to put * @param onlyifabsent if True, and don ' t change existing value * @param evict if False, the T     Able is in creation mode.                   * @return Previous value, or null if none */FINAL V putval (int hash, K key, V value, Boolean onlyifabsent, Boolean evict) {node<k,v>[] tab; Node<k,v> p;        int n, I; if (tab = table) = = NULL | |        (n = tab.length) = = 0)//tab is empty to create n = (Tab = resize ()). length; if (p = tab[i = (n-1) & hash]) = = NULL)//based on subscript, if not (no collision (hash value same)) then directly create tab[i] = Newnod        E (hash, key, value, NULL); else {//If a collision occurs the following processing node<k,v> e;            K K; if (P.hash = = Hash && (k = p.key) = = Key | |            (Key! = null && key.equals (k))))    e = p; else if (P instanceof TreeNode)//For the case of the red black number E = ((treenode<k,v>) p). Puttreeval (This            , tab, hash, key, value);                     else {//For the case of a linked list, plain node for (int bincount = 0;; ++bincount) {                        if ((e = p.next) = = null) {P.next = NewNode (hash, key, value, NULL);//List Save if (bincount >= treeify_threshold-1)//-1 for 1st treeifybin (tab, HA                SH);                    If the linked list is longer than 8, turn red-black tree break; } if (E.hash = = Hash && (k = e.key) = = Key | |                        (Key! = null && key.equals (k))))                    Break                p = e;                      }} if (E! = null) {//Existing mapping for key//write, and return oldvalueV oldValue = E.value;                if (!onlyifabsent | | oldValue = = NULL) E.value = value;                Afternodeaccess (e);            return oldValue;        }} ++modcount; if (++size > Threshold)//Over load factor*current capacity,resizeResize ();        Afternodeinsertion (evict);    return null; }


Can see the first argument when the hash of key, as follows

/** * computes Key.hashcode () and spreads (xors) higher bits of hash * to lower.  Because The table uses Power-of-two masking, sets of * hashes that vary only in bits above the current mask would * Always collide.  (among known examples is sets of Float keys * Holding consecutive whole numbers in small tables.) So we * apply a transform that spreads the impact of higher bits * downward. There is a tradeoff between speed, utility, and * quality of bit-spreading. Because Many common sets of hashes * is already reasonably distributed (so don ' t benefit from * spreading), and b Ecause we use trees to handle large sets of * collisions in bins, we just XOR some shifted bits in the * cheapest      Possible-to-reduce systematic lossage, as well as *-incorporate impact of the highest bits that would otherwise     * Never is used in index calculations because of table bounds.        */static final int hash (Object key) {int h; REturn (key = = null)?    0: (H = key.hashcode ()) ^ (h >>> 16); }

Considering the speed, effect, quality factor, it is the high 16bit and low 16bit of the key hashcode. Because most of the hashcode are now very well distributed, even if a collision has occurred with O(logn) the tree to do it. Only a different or a bit, it reduces the overhead of the system, and does not cause collisions due to high levels not participating in the Subscript calculation (table length compared to hours). And look back at Putval.

1. First determine if the node array table is null or the size is 0, if it is to initialize a tab and get its length. Resize () Later, let's look at node structure.

/** * Basic Hash bin node, used for most entries.     (See below for * TreeNode subclass, and in Linkedhashmap for its Entry subclass.)        */Static Class Node<k,v> implements map.entry<k,v> {final int hash;        Final K Key;        V value;        Node<k,v> Next;            Node (int hash, K key, V value, node<k,v> next) {This.hash = hash;            This.key = key;            This.value = value;        This.next = Next;        } Public final K GetKey () {return key;}        Public final V GetValue () {return value;}        Public final String toString () {return key + "=" + value;}        Public final int hashcode () {return Objects.hashcode (key) ^ Objects.hashcode (value);            Public final V SetValue (v newvalue) {v oldValue = value;            value = newvalue;        return oldValue;      Public final Boolean equals (Object o) {if (o = = this)          return true;                if (o instanceof map.entry) {map.entry<?,? > E = (map.entry<?,? >) o;                    if (Objects.equals (Key, E.getkey ()) && objects.equals (value, E.getvalue ()))            return true;        } return false; }    }


Node implements the form of a list, which is used to store hash, key, and value without collisions, and if collisions occur, they are stored with TreeNode, inherit from entry, and eventually inherit from node

/**     * Entry for Tree bins. Extends Linkedhashmap.entry (which in turn     * Extends Node) So can be used as extension of either regular or     * link Ed node.     */    static final class Treenode<k,v> extends Linkedhashmap.entry<k,v> {        treenode<k,v> Parent ;  Red-black Tree links        Treenode<k,v> left;        Treenode<k,v> right;        Treenode<k,v> prev;    needed to unlink next upon deletion        Boolean red;        TreeNode (int hash, K key, V Val, node<k,v> next) {            super (hash, Key, Val, next);        } ......}


2. Remove node from tab with (N-1) & hash, if not present, use hash, Key, value, null as parameter new node, save to tab with (N-1) & Hash as Subscript

3. If there is a value in the subscript, that is, node exists. If it is TreeNode, the tree node is stored with Puttreeval. Otherwise, it is stored as a linked list, and is converted to red-black tree storage If the list length exceeds 8.

4. Replace the old value if the node already exists (guaranteed uniqueness of key)

5. If the Bucket (node array) is full (over load factor*current capacity), it will be resize.

Summary: Put stored procedure: When the k/v is passed to the put method, it calls Hashcode compute hash to get node position, further storage, HashMap will automatically adjust the capacity according to the current node occupancy (more than load Facotr The resize is twice times the original). Visible if you do not overwrite hashcode, you cannot store correctly.


3) Get get
Read the put, and then look at the get
/** * Returns the value to which the specified key was mapped, * or {@code null} If this map contains no mapping fo     R the key. * * <p>more formally, if this map contains a mapping from a key * {@code k} to a value {@code V} such that { @code (key==null k==null: * key.equals (k))}, then this method returns {@code V};  otherwise * It returns {@code null}.     (There can is at the most one such mapping.) * * <p>a return value of {@code null} does not <i>necessarily</i> * indicate that the map Contai NS No mapping for the key;     It ' s also * possible that the map explicitly maps the key to {@code null}.     * The {@link #containsKey ContainsKey} operation May is used to * distinguish these and cases.        * * @see #put (object, Object) */Public V get (object key) {node<k,v> E; Return (E = getnode (hash (key), key) = = null?    Null:e.value; }

The Get method also uses a hash (), which is the hash and key of key to get node, the value returned is node's Value property. The following main look at the GetNode method can be
/** * Implements Map.get and Related methods * * @param hash hash for key * @param key The key * @retu  RN the node, or null if none */FINAL node<k,v> getnode (int hash, Object key) {node<k,v>[] tab; Node<k,v> First, E; int n;        K K; if (tab = table) = null && (n = tab.length) > 0 && (first = tab[(n-1) & hash])! = Nu                LL) {//map exists in the case that does not exist then directly returns null if (First.hash = = Hash &&//Always check first node (k = first.key) = = Key | |     (Key! = null && key.equals (k))))            The first direct hit of return; if ((e = first.next)! = null) {//If the first one dies, get the next node if (instanceof Treenod   e) return ((treenode<k,v>) first). Gettreenode (hash, key);                  If the next node is TreeNode, then use Gettreenode to get do {if (E.hash = = Hash &&      (k = e.key) = = Key | |    (Key! = null && key.equals (k))))                Loop the node list until hit return e;            } while ((e = e.next) = null);    }} return null; }

1) The first Direct hit 2) Otherwise, get the next node, if it is a red-black tree, then get from the red-black tree, otherwise loop the node list until hit. The hit condition is equal hash and key is the same (basic type = =, custom class with equals).
Summary: When getting an object, we pass K to get, which calls Hashcode to compute the hash to get the node position, and further calls the = = or the Equals () method to determine the key-value pair. Visible in order to obtain correctly, overwrite hashcode and Equals method
Digression: When the list length is more than 8, java8 with red and black trees instead of the linked list, the purpose is to improve performance, here does not expand. HashMap is based on the implementation of the map interface, when storing a key value pair, it can receive null key value, non-synchronous, HashMap store entry (hash, key, value, next) object.
3, why overwrite equals when to cover hashcode through the HashMap implementation principle, you can see when the custom class as a key value exists must do this, but not as a key value can choose not to do (but for the sake of specification, or to overwrite, So it becomes necessary). If you comment out the equals or hashcode in the test code, you will not get the correct result:
public class PhoneNumber {private int areacode;    private int prefix;    private int linenumber;        Public PhoneNumber (int areacode, int prefix, int linenumber) {this.areacode = AreaCode;        This.prefix = prefix;    This.linenumber = linenumber; }//@Override//public boolean equals (Object o) {//if (this = = O) return true;//if (o = = NULL | | get Class ()! = O.getclass ()) return false;////phonenumber that = (PhoneNumber) o;////if (areacode! = That.area    Code) return false;//if (prefix! = that.prefix) return false;//return linenumber = = that.linenumber;//        } @Override public int hashcode () {int result = AreaCode;        result = * result + prefix;        result = * result + linenumber;    return result; } public static void Main (string[] args) {map<phonenumber,string> phonenumberstringmap = new hashmap<ph        Onenumber,string> (); Phonenumberstringmap.put (New PhoneNumber (123, 456, 7890), "Honghailiang");    System.out.println (Phonenumberstringmap.get (New PhoneNumber (123, 456, 7890))); }}
All of the above results are null;

Digression the base type in Java can be a key value, including the String class, which already covers the Equals method and the Hashcode method.????

"Java Combat" source code parsing why overwrite equals method always overwrite Hashcode method

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.