1、概念
1、操作數的值
基本數據類(lèi)型變量
在Java中有八種基本數據類(lèi)型:
浮點(diǎn)型:float(4 byte), double(8 byte)
整型:byte(1 byte), short(2 byte), int(4 byte) , long(8 byte)
字符型: char(2 byte)
布爾型: boolean(JVM規范沒(méi)有明確規定其所占的空間大小,僅規定其只能夠取字面值”true”和”false”)
對于這八種基本數據類(lèi)型的變量,變量直接存儲的是“值”。因此,在使用關(guān)系操作符 == 來(lái)進(jìn)行比較時(shí),比較的就是“值”本身。要注意的是,浮點(diǎn)型和整型都是有符號類(lèi)型的(最高位僅用于表示正負,不參與計算【以 byte 為例,其范圍為 -2^7 ~ 2^7 - 1,-0即-128】),而char是無(wú)符號類(lèi)型的(所有位均參與計算,所以char類(lèi)型取值范圍為0~2^16-1)。
String str1; 這句話(huà)聲明了一個(gè)引用類(lèi)型的變量,此時(shí)它并沒(méi)有和任何對象關(guān)聯(lián)。
而通過(guò) new 來(lái)產(chǎn)生一個(gè)對象,并將這個(gè)對象和str1進(jìn)行綁定:
str1= new String("hello");那么 str1 就指向了這個(gè)對象,此時(shí)引用變量str1中存儲的是它指向的對象在內存中的存儲地址,并不是“值”本身,也就是說(shuō)并不是直接存儲的字符串”hello”。這里面的引用和 C/C++ 中的指針很類(lèi)似。
2、小結
因此,對于關(guān)系操作符 ==:
1、來(lái)源
equals方法是基類(lèi)Object中的實(shí)例方法,因此對所有繼承于Object的類(lèi)都會(huì )有該方法。
在 Object 中的聲明:
public boolean equals(Object obj) {}2、equals方法的作用
初衷 : 判斷兩個(gè)對象的 content 是否相同
為了更直觀(guān)地理解equals方法的作用,我們先看Object類(lèi)中equals方法的實(shí)現。
public boolean equals(Object obj) { return (this == obj); }很顯然,在Object類(lèi)中,equals方法是用來(lái)比較兩個(gè)對象的引用是否相等,即是否指向同一個(gè)對象。
但我們都知道,下面代碼輸出為 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)); }}原來(lái)是 String 類(lèi)重寫(xiě)了 equals 方法:
public boolean equals(Object anObject) { // 方法簽名與 Object類(lèi) 中的一致 if (this == anObject) { // 先判斷引用是否相同(是否為同一對象), return true; } if (anObject instanceof String) { // 再判斷類(lèi)型是否一致, // 最后判斷內容是否一致. 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;}即對于諸如“字符串比較時(shí)用的什么方法,內部實(shí)現如何?”之類(lèi)問(wèn)題的回答即為:
使用equals方法,內部實(shí)現分為三個(gè)步驟:
Java 中所有內置的類(lèi)的 equals 方法的實(shí)現步驟均是如此,特別是諸如 Integer,Double 等包裝器類(lèi)。
3、equals 重寫(xiě)原則
對象內容的比較才是設計equals()的真正目的,Java語(yǔ)言對equals()的要求如下,這些要求是重寫(xiě)該方法時(shí)必須遵循的:
對稱(chēng)性: 如果x.equals(y)返回是“true”,那么y.equals(x)也應該返回是“true” ;
自反性: x.equals(x)必須返回是“true” ;
類(lèi)推性: 如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也應該返回是“true” ;
一致性: 如果x.equals(y)返回是“true”,只要x和y內容一直不變,不管你重復x.equals(y)多少次,返回都是“true” ;
對稱(chēng)性: 如果x.equals(y)返回是“true”,那么y.equals(x)也應該返回是“true”。
任何情況下,x.equals(null)【應使用關(guān)系比較符 ==】,永遠返回是“false”;x.equals(和x不同類(lèi)型的對象)永遠返回是“false”
4、小結
因此,對于 equals 方法:
1、hashCode 的來(lái)源
hashCode 方法是基類(lèi)Object中的 實(shí)例native方法,因此對所有繼承于Object的類(lèi)都會(huì )有該方法。
在 Object類(lèi) 中的聲明(native方法暗示這些方法是有實(shí)現體的,但并不提供實(shí)現體,因為其實(shí)現體是由非java語(yǔ)言在外面實(shí)現的):
public native int hashCode();2、哈希相關(guān)概念
我們首先來(lái)了解一下哈希表:
概念 : Hash 就是把任意長(cháng)度的輸入(又叫做預映射, pre-image),通過(guò)散列算法,變換成固定長(cháng)度的輸出(int),該輸出就是散列值。這種轉換是一種 壓縮映射,也就是說(shuō),散列值的空間通常遠小于輸入的空間。不同的輸入可能會(huì )散列成相同的輸出,從而不可能從散列值來(lái)唯一的確定輸入值。簡(jiǎn)單的說(shuō),就是一種將任意長(cháng)度的消息壓縮到某一固定長(cháng)度的消息摘要的函數。
應用–數據結構 : 數組的特點(diǎn)是:尋址容易,插入和刪除困難; 而鏈表的特點(diǎn)是:尋址困難,插入和刪除容易。那么我們能不能綜合兩者的特性,做出一種尋址容易,插入和刪除也容易的數據結構?答案是肯定的,這就是我們要提起的哈希表,哈希表有多種不同的實(shí)現方法,我接下來(lái)解釋的是最常用的一種方法——拉鏈法,我們可以理解為 “鏈表的數組”,如圖:
左邊很明顯是個(gè)數組,數組的每個(gè)成員是一個(gè)鏈表。該數據結構所容納的所有元素均包含一個(gè)指針,用于元素間的鏈接。我們根據元素的自身特征把元素分配到不同的鏈表中去,也是根據這些特征,找到正確的鏈表,再從鏈表中找出這個(gè)元素。其中,將根據元素特征計算元素數組下標的方法就是散列法。
拉鏈法的適用范圍 : 快速查找,刪除的基本數據結構,通常需要總數據量可以放入內存。
3、hashCode 簡(jiǎn)述
在 Java 中,由 Object 類(lèi)定義的 hashCode 方法會(huì )針對不同的對象返回不同的整數。(這是通過(guò)將該對象的內部地址轉換成一個(gè)整數來(lái)實(shí)現的,但是 JavaTM 編程語(yǔ)言不需要這種實(shí)現技巧)。
hashCode 的常規協(xié)定是:
在 Java 應用程序執行期間,在對同一對象多次調用 hashCode 方法時(shí),必須一致地返回相同的整數,前提是將對象進(jìn)行 equals 比較時(shí)所用的信息沒(méi)有被修改。從某一應用程序的一次執行到同一應用程序的另一次執行,該整數無(wú)需保持一致。
如果根據 equals(Object) 方法,兩個(gè)對象是相等的,那么對這兩個(gè)對象中的每個(gè)對象調用 hashCode 方法都必須生成相同的整數結果。
如果根據 equals(java.lang.Object) 方法,兩個(gè)對象不相等,那么對這兩個(gè)對象中的任一對象上調用 hashCode 方法 不要求 一定生成不同的整數結果。但是,程序員應該意識到,為不相等的對象生成不同整數結果可以提高哈希表的性能。
要想進(jìn)一步了解 hashCode 的作用,我們必須先要了解Java中的容器,因為 HashCode 只是在需要用到哈希算法的數據結構中才有用,比如 HashSet, HashMap 和 Hashtable。
Java中的集合(Collection)有三類(lèi),一類(lèi)是List,一類(lèi)是Queue,再有一類(lèi)就是Set。 前兩個(gè)集合內的元素是有序的,元素可以重復;最后一個(gè)集合內的元素無(wú)序,但元素不可重復。
那么, 這里就有一個(gè)比較嚴重的問(wèn)題:要想保證元素不重復,可兩個(gè)元素是否重復應該依據什么來(lái)判斷呢? 這就是 Object.equals 方法了。但是,如果每增加一個(gè)元素就檢查一次,那么當元素很多時(shí),后添加到集合中的元素比較的次數就非常多了。 也就是說(shuō),如果集合中現在已經(jīng)有1000個(gè)元素,那么第1001個(gè)元素加入集合時(shí),它就要調用1000次equals方法。這顯然會(huì )大大降低效率。于是,Java采用了哈希表的原理。 這樣,我們對每個(gè)要存入集合的元素使用哈希算法算出一個(gè)值,然后根據該值計算出元素應該在數組的位置。所以,當集合要添加新的元素時(shí),可分為兩個(gè)步驟:
先調用這個(gè)元素的 hashCode 方法,然后根據所得到的值計算出元素應該在數組的位置。如果這個(gè)位置上沒(méi)有元素,那么直接將它存儲在這個(gè)位置上;
如果這個(gè)位置上已經(jīng)有元素了,那么調用它的equals方法與新元素進(jìn)行比較:相同的話(huà)就不存了,否則,將其存在這個(gè)位置對應的鏈表中(Java 中 HashSet, HashMap 和 Hashtable的實(shí)現總將元素放到鏈表的表頭)。
4、equals 與 hashCode
前提: 談到hashCode就不得不說(shuō)equals方法,二者均是Object類(lèi)里的方法。由于Object類(lèi)是所有類(lèi)的基類(lèi),所以一切類(lèi)里都可以重寫(xiě)這兩個(gè)方法。
5、實(shí)現例證
hashCode()在object類(lèi)中定義如下:
public native int hashCode();說(shuō)明是一個(gè)本地方法,它的實(shí)現是根據本地機器相關(guān)的。
String 類(lèi)是這樣重寫(xiě)它的:
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; }}對程序的解釋?zhuān)?code>h = s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1],由此可以看出,對象的hash地址不一定是實(shí)際的內存地址。
聯(lián)系客服