Java 中的 ==, equals 與 hashCode 的區別與聯絡
一、概述
1、概念
== : 該操作符生成的是一個boolean結果,它計算的是運算元的值之間的關係 equals : Object 的 例項方法,比較兩個物件的content是否相同 hashCode : Object 的 native方法 , 獲取物件的雜湊值,用於確定該物件在雜湊表中的索引位置,它實際上是一個int型整數
二、關係操作符 ==
1、運算元的值
基本資料型別變數
在Java中有八種基本資料型別:
浮點型:float(4 byte), double(8 byte)
整型:byte(1 byte), short(2 byte), int(4 byte) , long(8 byte)
字元型: char(2 byte)
布林型: boolean(JVM規範沒有明確規定其所佔的空間大小,僅規定其只能夠取字面值”true”和”false”)
對於這八種基本資料型別的變數,變數直接儲存的是“值”。因此,在使用關係操作符 == 來進行比較時,比較的就是“值”本身。要注意的是,浮點型和整型都是有符號型別的(最高位僅用於表示正負,不參與計算【以 byte 為例,其範圍為 -2^7 ~ 2^7 - 1,-0即-128】),而char是無符號型別的(所有位均參與計算,所以char型別取值範圍為0~2^16-1)。
引用型別變數 在Java中,引用型別的變數儲存的並不是“值”本身,而是與其關聯的物件在記憶體中的地址。比如下面這行程式碼,
String str1;1
這句話聲明瞭一個引用型別的變數,此時它並沒有和任何物件關聯。 而通過 new 來產生一個物件,並將這個物件和str1進行繫結:
str1= new String("hello");1
那麼 str1 就指向了這個物件,此時引用變數str1中儲存的是它指向的物件在記憶體中的儲存地址,並不是“值”本身,也就是說並不是直接儲存的字串”hello”。這裡面的引用和 C/C++ 中的指標很類似。
2、小結
因此,對於關係操作符 ==:
若運算元的型別是基本資料型別,則該關係操作符判斷的是左右兩邊運算元的值是否相等 若運算元的型別是引用資料型別,則該關係操作符判斷的是左右兩邊運算元的記憶體地址是否相同。也就是說,若此時返回true,則該操作符作用的一定是同一個物件。
三、equals方法
1、來源 equals方法是基類Object中的例項方法,因此對所有繼承於Object的類都會有該方法。 在 Object 中的宣告:
public boolean equals(Object obj) {}1
2、equals方法的作用 初衷 : 判斷兩個物件的 content 是否相同
為了更直觀地理解equals方法的作用,我們先看Object類中equals方法的實現。
public boolean equals(Object obj) { return (this == obj); }123
很顯然,在Object類中,equals方法是用來比較兩個物件的引用是否相等,即是否指向同一個物件。
但我們都知道,下面程式碼輸出為 true:
public class Main { public static void main(String[] args) { String str1 = new String("hello"); String str2 = new String("hello");
System.out.println(str1.equals(str2)); } }12345678
原來是 String 類重寫了 equals 方法:
public boolean equals(Object anObject) { // 方法簽名與 Object類 中的一致 if (this == anObject) { // 先判斷引用是否相同(是否為同一物件), return true; } if (anObject instanceof String) { // 再判斷型別是否一致, // 最後判斷內容是否一致. String anotherString = (String)anObject; int n = count; if (n == anotherString.count) { char v1[] = value; char v2[] = anotherString.value; int i = offset; int j = anotherString.offset; while (n-- != 0) { if (v1[i++] != v2[j++]) return false; } return true; } } return false; }12345678910111213141516171819202122
即對於諸如“字串比較時用的什麼方法,內部實現如何?”之類問題的回答即為:
使用equals方法,內部實現分為三個步驟:
先 比較引用是否相同(是否為同一物件), 再 判斷型別是否一致(是否為同一型別), 最後 比較內容是否一致
Java 中所有內建的類的 equals 方法的實現步驟均是如此,特別是諸如 Integer,Double 等包裝器類。
3、equals 重寫原則
物件內容的比較才是設計equals()的真正目的,Java語言對equals()的要求如下,這些要求是重寫該方法時必須遵循的:
對稱性: 如果x.equals(y)返回是“true”,那麼y.equals(x)也應該返回是“true” ; 自反性: x.equals(x)必須返回是“true” ; 類推性: 如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那麼z.equals(x)也應該返回是“true” ; 一致性: 如果x.equals(y)返回是“true”,只要x和y內容一直不變,不管你重複x.equals(y)多少次,返回都是“true” ; 對稱性: 如果x.equals(y)返回是“true”,那麼y.equals(x)也應該返回是“true”。 任何情況下,x.equals(null)【應使用關係比較符 ==】,永遠返回是“false”;x.equals(和x不同型別的物件)永遠返回是“false”
4、小結 因此,對於 equals 方法:
其本意 是 比較兩個物件的 content 是否相同 必要的時候,我們需要重寫該方法,避免違背本意,且要遵循上述原則
四、hashCode 方法
1、hashCode 的來源 hashCode 方法是基類Object中的 例項native方法,因此對所有繼承於Object的類都會有該方法。 在 Object類 中的宣告(native方法暗示這些方法是有實現體的,但並不提供實現體,因為其實現體是由非java語言在外面實現的):
public native int hashCode();1
2、雜湊相關概念 我們首先來了解一下雜湊表:
概念 : Hash 就是把任意長度的輸入(又叫做預對映, pre-image),通過雜湊演算法,變換成固定長度的輸出(int),該輸出就是雜湊值。這種轉換是一種 壓縮對映,也就是說,雜湊值的空間通常遠小於輸入的空間。不同的輸入可能會雜湊成相同的輸出,從而不可能從雜湊值來唯一的確定輸入值。簡單的說,就是一種將任意長度的訊息壓縮到某一固定長度的訊息摘要的函式。 應用–資料結構 : 陣列的特點是:定址容易,插入和刪除困難; 而連結串列的特點是:定址困難,插入和刪除容易。那麼我們能不能綜合兩者的特性,做出一種定址容易,插入和刪除也容易的資料結構?答案是肯定的,這就是我們要提起的雜湊表,雜湊表有多種不同的實現方法,我接下來解釋的是最常用的一種方法——拉鍊法,我們可以理解為 “連結串列的陣列”,如圖:
圖1 雜湊表示例
左邊很明顯是個陣列,陣列的每個成員是一個連結串列。該資料結構所容納的所有元素均包含一個指標,用於元素間的連結。我們根據元素的自身特徵把元素分配到不同的連結串列中去,也是根據這些特徵,找到正確的連結串列,再從連結串列中找出這個元素。其中,將根據元素特徵計算元素陣列下標的方法就是雜湊法。 拉鍊法的適用範圍 : 快速查詢,刪除的基本資料結構,通常需要總資料量可以放入記憶體。 要點 : hash函式選擇,針對字串,整數,排列,具體相應的hash方法; 碰撞處理,一種是open hashing,也稱為拉鍊法,另一種就是closed hashing,也稱開地址法,opened addressing。
3、hashCode 簡述 在 Java 中,由 Object 類定義的 hashCode 方法會針對不同的物件返回不同的整數。(這是通過將該物件的內部地址轉換成一個整數來實現的,但是 JavaTM 程式語言不需要這種實現技巧)。
hashCode 的常規協定是:
在 Java 應用程式執行期間,在對同一物件多次呼叫 hashCode 方法時,必須一致地返回相同的整數,前提是將物件進行 equals 比較時所用的資訊沒有被修改。從某一應用程式的一次執行到同一應用程式的另一次執行,該整數無需保持一致。 如果根據 equals(Object) 方法,兩個物件是相等的,那麼對這兩個物件中的每個物件呼叫 hashCode 方法都必須生成相同的整數結果。 如果根據 equals(java.lang.Object) 方法,兩個物件不相等,那麼對這兩個物件中的任一物件上呼叫 hashCode 方法 不要求 一定生成不同的整數結果。但是,程式設計師應該意識到,為不相等的物件生成不同整數結果可以提高雜湊表的效能。 要想進一步瞭解 hashCode 的作用,我們必須先要了解Java中的容器,因為 HashCode 只是在需要用到雜湊演算法的資料結構中才有用,比如 HashSet, HashMap 和 Hashtable。
Java中的集合(Collection)有三類,一類是List,一類是Queue,再有一類就是Set。 前兩個集合內的元素是有序的,元素可以重複;最後一個集合內的元素無序,但元素不可重複。
那麼, 這裡就有一個比較嚴重的問題:要想保證元素不重複,可兩個元素是否重複應該依據什麼來判斷呢? 這就是 Object.equals 方法了。但是,如果每增加一個元素就檢查一次,那麼當元素很多時,後新增到集合中的元素比較的次數就非常多了。 也就是說,如果集合中現在已經有1000個元素,那麼第1001個元素加入集合時,它就要呼叫1000次equals方法。這顯然會大大降低效率。於是,Java採用了雜湊表的原理。 這樣,我們對每個要存入集合的元素使用雜湊演算法算出一個值,然後根據該值計算出元素應該在陣列的位置。所以,當集合要新增新的元素時,可分為兩個步驟: 先呼叫這個元素的 hashCode 方法,然後根據所得到的值計算出元素應該在陣列的位置。如果這個位置上沒有元素,那麼直接將它儲存在這個位置上; 如果這個位置上已經有元素了,那麼呼叫它的equals方法與新元素進行比較:相同的話就不存了,否則,將其存在這個位置對應的連結串列中(Java 中 HashSet, HashMap 和 Hashtable的實現總將元素放到連結串列的表頭)。
4、equals 與 hashCode
前提: 談到hashCode就不得不說equals方法,二者均是Object類裡的方法。由於Object類是所有類的基類,所以一切類裡都可以重寫這兩個方法。
原則 1 : 如果 x.equals(y) 返回 “true”,那麼 x 和 y 的 hashCode() 必須相等 ; 原則 2 : 如果 x.equals(y) 返回 “false”,那麼 x 和 y 的 hashCode() 有可能相等,也有可能不等 ; 原則 3 : 如果 x 和 y 的 hashCode() 不相等,那麼 x.equals(y) 一定返回 “false” ; 原則 4 : 一般來講,equals 這個方法是給使用者呼叫的,而 hashcode 方法一般使用者不會去呼叫 ; 原則 5 : 當一個物件型別作為集合物件的元素時,那麼這個物件應該擁有自己的equals()和hashCode()設計,而且要遵守前面所說的幾個原則。
5、實現例證
hashCode()在object類中定義如下:
public native int hashCode();1
說明是一個本地方法,它的實現是根據本地機器相關的。
String 類是這樣重寫它的:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; //成員變數1
/** The offset is the first index of the storage that is used. */ private final int offset; //成員變數2
/** The count is the number of characters in the String. */ private final int count; //成員變數3
/** Cache the hash code for the string */ private int hash; // Default to 0 //非成員變數
public int hashCode() { int h = hash; int len = count; //用到成員變數3 if (h == 0 && len > 0) { int off = offset; //用到成員變數2 char val[] = value; //用到成員變數1 for (int i = 0; i < len; i++) { h = 31*h + val[off++]; //遞推公式 } hash = h; } return h; } }1234567891011121314151617181920212223242526272829
對程式的解釋:h = s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1],由此可以看出,物件的hash地址不一定是實際的記憶體地址。
五、小結
hashcode是系統用來快速檢索物件而使用 equals方法本意是用來判斷引用的物件是否一致 重寫equals方法和hashcode方法時,equals方法中用到的成員變數也必定會在hashcode方法中用到,只不過前者作為比較項,後者作為生成摘要的資訊項,本質上所用到的資料是一樣的,從而保證二者的一致性