1. 程式人生 > >淺談---對equals、hashCode、“==”的理解

淺談---對equals、hashCode、“==”的理解

在我們平常的學習中,經常會涉及到equals、hashCode、“==”這三者,對於這三者我經常混淆不清。因此,我自行總結了一下。

先說下在我們程式設計中用到的比較不容易區分的equals和hashCode的區別

equals和hashCode都是Object類中的方法,原始碼中是這樣解釋的。 可跳過原始碼,接著往下看~~~

 public native int hashCode();

    /**
     * Indicates whether some other object is "equal to" this one.
     * <p>
     * The {@code equals} method implements an equivalence relation
     * on non-null object references:
     * <ul>
     * <li>It is <i>reflexive</i>: for any non-null reference value
     *     {@code x}, {@code x.equals(x)} should return
     *     {@code true}.
     * <li>It is <i>symmetric</i>: for any non-null reference values
     *     {@code x} and {@code y}, {@code x.equals(y)}
     *     should return {@code true} if and only if
     *     {@code y.equals(x)} returns {@code true}.
     * <li>It is <i>transitive</i>: for any non-null reference values
     *     {@code x}, {@code y}, and {@code z}, if
     *     {@code x.equals(y)} returns {@code true} and
     *     {@code y.equals(z)} returns {@code true}, then
     *     {@code x.equals(z)} should return {@code true}.
     * <li>It is <i>consistent</i>: for any non-null reference values
     *     {@code x} and {@code y}, multiple invocations of
     *     {@code x.equals(y)} consistently return {@code true}
     *     or consistently return {@code false}, provided no
     *     information used in {@code equals} comparisons on the
     *     objects is modified.
     * <li>For any non-null reference value {@code x},
     *     {@code x.equals(null)} should return {@code false}.
     * </ul>
     * <p>
     * The {@code equals} method for class {@code Object} implements
     * the most discriminating possible equivalence relation on objects;
     * that is, for any non-null reference values {@code x} and
     * {@code y}, this method returns {@code true} if and only
     * if {@code x} and {@code y} refer to the same object
     * ({@code x == y} has the value {@code true}).
     * <p>
     * Note that it is generally necessary to override the {@code hashCode}
     * method whenever this method is overridden, so as to maintain the
     * general contract for the {@code hashCode} method, which states
     * that equal objects must have equal hash codes.
     *
     * @param   obj   the reference object with which to compare.
     * @return  {@code true} if this object is the same as the obj
     *          argument; {@code false} otherwise.
     * @see     #hashCode()
     * @see     java.util.HashMap
     */


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

    /**
     * Creates and returns a copy of this object.  The precise meaning
     * of "copy" may depend on the class of the object. The general
     * intent is that, for any object {@code x}, the expression:
     * <blockquote>
     * <pre>
     * x.clone() != x</pre></blockquote>
     * will be true, and that the expression:
     * <blockquote>
     * <pre>
     * x.clone().getClass() == x.getClass()</pre></blockquote>
     * will be {@code true}, but these are not absolute requirements.
     * While it is typically the case that:
     * <blockquote>
     * <pre>
     * x.clone().equals(x)</pre></blockquote>
     * will be {@code true}, this is not an absolute requirement.
     * <p>
     * By convention, the returned object should be obtained by calling
     * {@code super.clone}.  If a class and all of its superclasses (except
     * {@code Object}) obey this convention, it will be the case that
     * {@code x.clone().getClass() == x.getClass()}.
     * <p>
     * By convention, the object returned by this method should be independent
     * of this object (which is being cloned).  To achieve this independence,
     * it may be necessary to modify one or more fields of the object returned
     * by {@code super.clone} before returning it.  Typically, this means
     * copying any mutable objects that comprise the internal "deep structure"
     * of the object being cloned and replacing the references to these
     * objects with references to the copies.  If a class contains only
     * primitive fields or references to immutable objects, then it is usually
     * the case that no fields in the object returned by {@code super.clone}
     * need to be modified.
     * <p>
     * The method {@code clone} for class {@code Object} performs a
     * specific cloning operation. First, if the class of this object does
     * not implement the interface {@code Cloneable}, then a
     * {@code CloneNotSupportedException} is thrown. Note that all arrays
     * are considered to implement the interface {@code Cloneable} and that
     * the return type of the {@code clone} method of an array type {@code T[]}
     * is {@code T[]} where T is any reference or primitive type.
     * Otherwise, this method creates a new instance of the class of this
     * object and initializes all its fields with exactly the contents of
     * the corresponding fields of this object, as if by assignment; the
     * contents of the fields are not themselves cloned. Thus, this method
     * performs a "shallow copy" of this object, not a "deep copy" operation.
     * <p>
     * The class {@code Object} does not itself implement the interface
     * {@code Cloneable}, so calling the {@code clone} method on an object
     * whose class is {@code Object} will result in throwing an
     * exception at run time.
     *
     * @return     a clone of this instance.
     * @throws  CloneNotSupportedException  if the object's class does not
     *               support the {@code Cloneable} interface. Subclasses
     *               that override the {@code clone} method can also
     *               throw this exception to indicate that an instance cannot
     *               be cloned.
     * @see java.lang.Cloneable
     */

這兩者常出現在雜湊的資料結構中(HashSet、HashMap、LinkedHashSet或LinkedHashMap),在雜湊的資料結構定址中就用到這兩個方法(後期會更新Map集合和HashMap的使用)。對於這篇文章主要來談一下,equals和hasdCode的區別,“==”作為附帶品。

在雜湊資料結構HashMap中定址遵循的原則是: 當equals()相等時,hascode()必定相等。 相反hascode()相等時,equals()不一定相等。 只有當equals和hascode同時相等時才能判斷到兩個值相等。

再繼續的深入瞭解一下: 1.關於equals()方法(來自於Java程式設計思想): 正確使用equals()方法必須滿足下列5個條件: (1)、自反性。對於任意x,x.equals(x)一定返回true (2)、對稱性。對任意x和y,如果y.equals(x)返回true,則x.equals(y)也返回true。 (3)、傳遞性。對任意x、y、z,如果有x.equals(y)返回true,y.equals(z)返回true,則x.equals(z)一定返回true。 (4)、一致性。對任意x和y,如果物件中用於等價比較的資訊沒有改變,那麼無論呼叫x.equals(y)多少次,返回的結果應該保持一致,要麼一直是true,要麼一直是false。 (5)、對於任何不是null的x,x.equals(null)一定返回false。 在Object類中的equals()方法比較的是物件的地址。(此處的地址所指的是磁碟的邏輯地址並非實體地址,對於邏輯地址與實體地址的概念可以查詢作業系統相關書籍,在作業系統中,一般都不會出現真正的實體地址) 2. hashCode()方法在電腦科學的術語中稱為雜湊函式。 什麼是hashCode? hashCode是jdk根據物件的地址 或者字串或者數字算 出來的int型別的數值 雜湊的價值: 雜湊使得查詢得以快速進行。在Map集合中,對於鍵的儲存沒有按照任何的順序儲存,所以要查詢的話是簡單的線性查詢,而線性查詢又是最慢的查詢方式。 雜湊的作用使得鍵儲存在某處,以便能很快的找到。而儲存一組元素最快的資料結構是陣列(陣列不能調整容量),所以用陣列儲存鍵的資訊。

問題:我們希望在Map中儲存數量不確定的值,但是如果鍵的數量被陣列的容量限制了,該怎麼辦?
答:在陣列中儲存的不是鍵的本身,而是通過鍵物件生成一個數字,將其作為陣列的下標,這個數字就是雜湊碼,由定義在Object中的、且可能由你的類覆蓋的hashCode()方法生成。

關於equals方法,一致的約定是:

  • 重寫了euqls方法的物件必須同時重寫hashCode()方法。

  • 如果2個物件通過equals呼叫後返回是true,那麼這個2個物件的hashCode方法也必須返回同樣的int型雜湊碼

  • 如果2個物件通過equals返回false,他們的hashCode返回的值允許相同。

也就是說參與equals方法的欄位,都必須參與hashCode欄位的運算。

關於hashCode方法,一致的約定是:

  • 在某個執行時期間,只要物件的(欄位的)變化不會影響equals方法的決策結果,那麼,在這個期間,無論呼叫多少次hashCode,都必須返回同一個雜湊碼。

  • 通過equals呼叫返回true 的2個物件的hashCode一定一樣。

  • 通過equasl返回false 的2個物件的雜湊碼不需要不同,也就是他們的hashCode方法的返回值允許出現相同的情況。

總結:等價的(呼叫equals返回true)物件必須產生相同的雜湊碼。不等價的物件,不要求產生的雜湊碼不相同。

在使用雜湊的資料結構中,如果不為你的鍵覆蓋hashCode()和equals(),那麼使用雜湊的資料結構中就無法正確的處理鍵。

“==”和equals的區別!!!

“ == ” 號在比較基本資料型別時比較的是值,而用“ == ”號比較兩個物件時比較的是兩個物件的地址值。 equals方法通過原始碼我們發現,Object類中equals()方法底層依賴的是“ == ”號,那麼,在所有沒有重寫equals()方法的類中,呼叫equals()方法其實和使用“ == ”號的效果一樣,也是比較的地址值,然而,Java提供的所有類中,絕大多數類都重寫了equals()方法,重寫後的equals()方法一般都是比較兩個物件的值。

那麼怎麼去深層次一點去區分他們呢,那就繼續往下看。

對於“ == ”和equals方法的比較我們通常在引用資料型別中討論(基本資料型別都在堆上儲存,不用去比較兩者的不同)

基本資料型別和引用資料型別: 這裡簡單提一下,不做太多的討論(可自行查閱一下)。

  • Java中基本資料型別分為8種boolean、byte、short、int、long、float、double、char,儲存方式在記憶體的堆上儲存。
  • 引用資料型別是在棧上開闢空間,指向堆

基本資料型別和引用型別的區別主要在於基本資料型別是分配在棧上的,而引用型別是分配在堆上的

來道題分析:
  String s = new String("hello");
        String s1 = "hello";

        System.out.println(s == s1);
        System.out.println(s.equals(s1));
   編譯的結果為:
   false
   true

分析:對於==比較的是引用資料型別的引用是否相等,也就是所指向的記憶體地址是否一樣,這裡我們知道s所指向的記憶體地址是在堆記憶體中的,而s1是在字串常量池中的,雖然指向的字串內容都是“hello”,可是地址是不同的。

小結:對於equals就是比較引用地址所指向的記憶體地址鎖對應的內容是否一致的,也就是說==比較記憶體地址,而equals比較記憶體地址指向的內容是否一樣!

  • 注意:預設的equals方法也是使用了==,所以一般這個equals是由我們去覆寫的,如果不覆寫,那其實跟==沒什麼區別。

綜上,是我自己對這三者的總結,本人學程式設計一段時間,但還是菜鳥一枚,故打算好好紮實基礎,歡迎讀者找出文章的問題與不足之處,期望一塊努力學習程式設計,一塊成長。