前言
成都創(chuàng)新互聯(lián)公司服務(wù)項目包括明山網(wǎng)站建設(shè)、明山網(wǎng)站制作、明山網(wǎng)頁制作以及明山網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,明山網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到明山省份的部分城市,未來相信會繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
在程序設(shè)計中,有很多的“公約”,遵守約定去實現(xiàn)你的代碼,會讓你避開很多坑,這些公約是前人總結(jié)出來的設(shè)計規(guī)范。
Object類是Java中的萬類之祖,其中,equals和hashCode是2個非常重要的方法。
這2個方法總是被人放在一起討論。最近在看集合框架,為了打基礎(chǔ),就決定把一些細(xì)枝末節(jié)清理掉。一次性搞清楚!
下面開始剖析。
public boolean equals(Object obj)
Object類中默認(rèn)的實現(xiàn)方式是 : return this == obj 。那就是說,只有this 和 obj引用同一個對象,才會返回true。
而我們往往需要用equals來判斷 2個對象是否等價,而非驗證他們的唯一性。這樣我們在實現(xiàn)自己的類時,就要重寫equals.
按照約定,equals要滿足以下規(guī)則。
一個例子
1 class Test 2 { 3 private int num; 4 private String data; 5 6 public boolean equals(Object obj) 7 { 8 if (this == obj) 9 return true; 10 11 if ((obj == null) || (obj.getClass() != this.getClass())) 12 return false; 13 //能執(zhí)行到這里,說明obj和this同類且非null。 14 Test test = (Test) obj; 15 return num == test.num&& (data == test.data || (data != null && data.equals(test.data))); 16 } 17 18 public int hashCode() 19 { 20 //重寫equals,也必須重寫hashCode。具體后面介紹。 24 } 25 26 }
equals編寫指導(dǎo)
Test類對象有2個字段,num和data,這2個字段代表了對象的狀態(tài),他們也用在equals方法中作為評判的依據(jù)。
在第8行,傳入的比較對象的引用和this做比較,這樣做是為了 save time ,節(jié)約執(zhí)行時間,如果this 和 obj是 對同一個堆對象的引用,那么,他們一定是qeuals 的。
接著,判斷obj是不是為null,如果為null,一定不equals,因為既然當(dāng)前對象this能調(diào)用equals方法,那么它一定不是null,非null 和 null當(dāng)然不等價。
然后,比較2個對象的運行時類,是否為同一個類。不是同一個類,則不equals。getClass返回的是 this 和obj的運行時類的引用。如果他們屬于同一個類,則返回的是同一個運行時類的引用。注意,一個類也是一個對象。
1、有些程序員使用下面的第二種寫法替代第一種比較運行時類的寫法。應(yīng)該避免這樣做。
if((obj == null) || (obj.getClass() != this.getClass())) return false; if(!(obj instanceof Test)) return false; // avoid 避免!
它違反了公約中的對稱原則。
例如:假設(shè)Dog擴(kuò)展了Aminal類。
dog instanceof Animal 得到true
animal instanceof Dog 得到false
這就會導(dǎo)致
animal.equls(dog) 返回true
dog.equals(animal) 返回false
僅當(dāng)Test類沒有子類的時候,這樣做才能保證是正確的。
2、按照第一種方法實現(xiàn),那么equals只能比較同一個類的對象,不同類對象永遠(yuǎn)是false。但這并不是強(qiáng)制要求的。一般我們也很少需要在不同的類之間使用equals。
3、在具體比較對象的字段的時候,對于基本值類型的字段,直接用 == 來比較(注意浮點數(shù)的比較,這是一個坑)對于引用類型的字段,你可以調(diào)用他們的equals,當(dāng)然,你也需要處理字段為null 的情況。對于浮點數(shù)的比較,我在看Arrays.binarySearch的源代碼時,發(fā)現(xiàn)了如下對于浮點數(shù)的比較的技巧:
if ( Double.doubleToLongBits(d1) == Double.doubleToLongBits(d2) ) //d1 和 d2 是double類型 if( Float.floatToIntBits(f1) == Float.floatToIntBits(f2) ) //f1 和 f2 是d2是float類型
4、并不總是要將對象的所有字段來作為equals 的評判依據(jù),那取決于你的業(yè)務(wù)要求。比如你要做一個家電功率統(tǒng)計系統(tǒng),如果2個家電的功率一樣,那就有足夠的依據(jù)認(rèn)為這2個家電對象等價了,至少在你這個業(yè)務(wù)邏輯背景下是等價的,并不關(guān)心他們的價錢啊,品牌啊,大小等其他參數(shù)。
5、最后需要注意的是,equals 方法的參數(shù)類型是Object,不要寫錯!
public int hashCode()
這個方法返回對象的散列碼,返回值是int類型的散列碼。
對象的散列碼是為了更好的支持基于哈希機(jī)制的Java集合類,例如 Hashtable, HashMap, HashSet 等。
關(guān)于hashCode方法,一致的約定是:
重寫了euqls方法的對象必須同時重寫hashCode()方法。
如果2個對象通過equals調(diào)用后返回是true,那么這個2個對象的hashCode方法也必須返回同樣的int型散列碼
如果2個對象通過equals返回false,他們的hashCode返回的值允許相同。(然而,程序員必須意識到,hashCode返回獨一無二的散列碼,會讓存儲這個對象的hashtables更好地工作。)
在上面的例子中,Test類對象有2個字段,num和data,這2個字段代表了對象的狀態(tài),他們也用在equals方法中作為評判的依據(jù)。那么, 在hashCode方法中,這2個字段也要參與hash值的運算,作為hash運算的中間參數(shù)。這點很關(guān)鍵,這是為了遵守:2個對象equals,那么 hashCode一定相同規(guī)則。
也是說,參與equals函數(shù)的字段,也必須都參與hashCode 的計算。
合乎情理的是:同一個類中的不同對象返回不同的散列碼。典型的方式就是根據(jù)對象的地址來轉(zhuǎn)換為此對象的散列碼,但是這種方式對于Java來說并不是唯一的要求的
的實現(xiàn)方式。通常也不是最好的實現(xiàn)方式。
相比于equals公認(rèn)實現(xiàn)約定,hashCode的公約要求是很容易理解的。有2個重點是hashCode方法必須遵守的。約定的第3點,其實就是第2點的
細(xì)化,下面我們就來看看對hashCode方法的一致約定要求。
總結(jié)一句話:等價的(調(diào)用equals返回true)對象必須產(chǎn)生相同的散列碼。不等價的對象,不要求產(chǎn)生的散列碼不相同。
hashCode編寫指導(dǎo)
在編寫hashCode時,你需要考慮的是,最終的hash是個int值,而不能溢出。不同的對象的hash碼應(yīng)該盡量不同,避免hash沖突。
那么如果做到呢?下面是解決方案。
1、定義一個int類型的變量 hash,初始化為 7。
接下來讓你認(rèn)為重要的字段(equals中衡量相等的字段)參入散列運,算每一個重要字段都會產(chǎn)生一個hash分量,為最終的hash值做出貢獻(xiàn)(影響)
運算方法參考表
重要字段var的類型 | 他生成的hash分量 |
---|---|
byte, char, short , int | (int)var |
long | (int)(var ^ (var >>> 32)) |
boolean | var?1:0 |
float | Float.floatToIntBits(var) |
double | long bits = Double.doubleToLongBits(var); 分量 = (int)(bits ^ (bits >>> 32)); |
引用類型 | (null == var ? 0 : var.hashCode()) |
最后把所有的分量都總和起來,注意并不是簡單的相加。選擇一個倍乘的數(shù)字31,參與計算。然后不斷地遞歸計算,直到所有的字段都參與了。
int hash = 7; hash = 31 * hash + 字段1貢獻(xiàn)分量; hash = 31 * hash + 字段2貢獻(xiàn)分量; ..... return hash;
說明,以下的內(nèi)容是我在google上找到并翻譯整理的,其中加入了自己的話和一些例子,便于理解,但我能保證這并不影響整體準(zhǔn)確性。
英文原文:http://www.javaranch.com/journal/2002/10/equalhash.html
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。