国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

Effective.Java 讀書筆記(9)關(guān)于HashCode

2019-11-06 09:11:58
字體:
供稿:網(wǎng)友

9.Always override hashCode when you override equals

大意為 在你重寫equals方法的時候要經(jīng)常重寫hashCode

有很多程序會錯誤的原因之一,就是當你重寫一個類的equals方法的時候忘記重寫它的hashCode了

請記住當你重寫一個類的equals方法的時候,一定要重寫hashCode,如果你不這樣做的話,就會違反了Object.hashCode的 通用規(guī)范,這通常會導(dǎo)致一些hash類的問題,比如HashMap,HashSet以及HashTable

以下有一些來自于Object類的規(guī)范 [javaSE6]: - 當你不斷在一個程序里面調(diào)用一個對象的hashCode方法,它總應(yīng)該返回一個相同的整形數(shù)值

需要注意的是,這個和重寫equals方法的規(guī)范中的一致性不大一樣,不要求在反復(fù)執(zhí)行相同的程序的情況下,返回一樣的值 

  - 如果兩個對象使用equals方法比較然后返回true的話,那么這兩個對象的hashCode應(yīng)該返回相同的數(shù)值 - 對于兩個對象使用equals方法比較返回false的情況,并不強制要求hashCode也不一樣

當然,對兩個不同的對象返回不同的hashCode值會提高hashTable的表現(xiàn)

在這里指出最為關(guān)鍵的部分,即第二個條件是最容易犯錯的,兩個對象用equals比較,一定要返回一樣的hashCode 兩個不一樣的實例可能由于修改了equals方法,可能邏輯上是相等的,但是Object的hashCode并不會去在意這些,只會簡單地返回不一樣的數(shù)值,并不會根據(jù)規(guī)范而返回相同值

舉一個簡單的例子來說,我們來看一個PhoneNumber的類,我們已經(jīng)重寫了它的構(gòu)造方法:

public final class PhoneNumber { PRivate final short areaCode; private final short prefix; private final short lineNumber; public PhoneNumber(int areaCode, int prefix, int lineNumber) { rangeCheck(areaCode, 999, "area code"); rangeCheck(prefix, 999, "prefix"); rangeCheck(lineNumber, 9999, "line number"); this.areaCode = (short) areaCode; this.prefix = (short) prefix; this.lineNumber = (short) lineNumber; } private static void rangeCheck(int arg, int max, String name) { if (arg < 0 || arg > max) throw new IllegalArgumentException(name +": " + arg); } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof PhoneNumber)) return false; PhoneNumber pn = (PhoneNumber)o; return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode; } // Broken - no hashCode method! ... // Remainder omitted}

好的,我們用一個HashMap來儲存這個類的實例

Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();m.put(new PhoneNumber(707, 867, 5309), "Jenny")

這個時候,你可能會認為以下這條語句會返回Jenny

m.get(new PhoneNumber(707 , 867 , 5309))

事實卻是它返回了一個null,明顯存在的問題就是沒有重寫hashCode方法導(dǎo)致這樣,修復(fù)這問題只需要重寫一個合適的hashCode方法即可

在這里要說一點,即使兩個實例運氣足夠好,散列到同一個bucket里面,HashMap的get方法也會返回null,只要hashCode不一樣HashMap就不會去檢查邏輯上的相等

下面給出一個無腦的解決的方法:

// The worst possible legal hash function - never use! @Override public int hashCode() { return 42; }

這樣看上去好像合法,但是你千萬別這樣用,它合法是由于它保證了相等的對象一定會有相同的hash code,但是這樣的做法太過于粗暴以至于讓所有的對象的hash code都一樣,所有的對象都散列到相同的bucket里面,HashTable就退化成鏈表了

一個好的hashCode方法對于不同的對象應(yīng)該返回不同的值,理想情況下,hashCode方法應(yīng)該均勻地分配數(shù)值給那些不相等的對象,但是實現(xiàn)起來好像比較困難,不過接近這樣的方案還是有的:

存一些連續(xù)的非零整形數(shù)值,比如說17,把它記為result

對于每一個重要的域,做下列的事情:

對這個域計算int類型的hash code,我們把這個hash code叫做c

如果這個域是布爾變量,返回(f?1:0)如果是byte,char,short或者是int,返回(int)f如果是long類型,返回 (int) (f ^ (f >>> 32)) 如果是float類型,返回 Float.floatToIntBits(f) 如果是double類型,先使用 Double.doubleToLongBits(f) 轉(zhuǎn)變?yōu)閘ong類型,再按long類型來計算

如果這個域是對象的引用,而且這個類在使用equals方法的時候會遞歸地來調(diào)用這個引用,那就直接遞歸地調(diào)用這個引用的hashCode方法,當然如果是需要更加復(fù)雜的比較,可以先計算出一個規(guī)范的表示,然后在這個規(guī)范的表示中去調(diào)用hashCode方法,如果該域是null,就直接返回0

返回0是比較傳統(tǒng)的做法,你也可以返回其他的

如果域是一個數(shù)組,你可以把元素看成是分離的域的組合,對于那些重要的域使用上述的原則進行計算,當然如果整個數(shù)組的元素都是重要的,必須要比較的,那你可以直接使用Arrays.hashCode方法

通過計算得到的result和c我們可以來更新result,公式為:result=result?31+c返回result結(jié)束對hashCode方法的編寫,并且檢查有沒有符合上文所說的那幾條規(guī)范

在對hash code進行計算的時候,你可能不會把一些“冗余的”域也計算進去,需要注意的是,那些可以由其他域計算而來的域稱為冗余的域,計算hash code的時候把它們忽略不理可能不是一件正確的事,很有可能就會導(dǎo)致對于第二個規(guī)范的違反

回看一下計算的過程,我們初始化了一個非零的值,這個值對于hash code的最后生成有著極大的影響,但是這個值不能是0,如果是0的話,那么初始化的域的影響就沒了,這樣就可能產(chǎn)生沖突,故17這個值是合適的

多維的對于不同類型的不同操作表現(xiàn)出了不錯的hash特性,另外選擇31作為因子是由于它是一個奇素數(shù),而且利用位運算很容易計算,只要右移5位減去1即可

目前使用素數(shù)還是不大明確其優(yōu)點,但傳統(tǒng)上是這么用的,在溢出的情況下能夠在一定意義上保留信息

我們使用PhoneNumber類來實際操作一次

@Override public int hashCode() { int result = 17; result = 31 * result + areaCode; result = 31 * result + prefix; result = 31 * result + lineNumber; return result;}

這樣的方法實現(xiàn)保證了邏輯相同的實例有著相同的hashCode,這個方法看似簡單,它的性能卻和Java平臺庫的函數(shù)性能上不相上下,十分簡單而且高效,將邏輯不同的實例散列到不同的bucket中

需要說明的是,如果計算hash code的代價開銷不小,你必須考慮把hash code緩存起來而不是每一次都重新計算

我們在PhoneNumber類上簡單實現(xiàn)一下

// Lazily initialized, cached hashCodeprivate volatile int hashCode; @Override public int hashCode() { int result = hashCode; if (result == 0) { result = 17; result = 31 * result + areaCode; result = 31 * result + prefix; result = 31 * result + lineNumber; hashCode = result; } return result;}

這里所提及的hash函數(shù)并不是最先進的,你可以利用數(shù)學(xué)和計算機科學(xué)理論的知識結(jié)合最前沿的論文探討一下這個函數(shù)的更好的實現(xiàn)方案,但是將一個對象重要的域在hash code的計算中忽略以試圖提高性能的做法絕對是完全錯誤的,最多加快了方法的速度,但對于整個hash集合的性能來說是得不償失的

目前Integer類的hashCode方法都是返回實確的值,這并不是一個好的辦法,希望有一天可以被修改成更為高效的方法


發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 乌兰浩特市| 广东省| 合江县| 游戏| 永福县| 郸城县| 武清区| 姚安县| 大同市| 新宁县| 宜君县| 东方市| 松潘县| 通化县| 扎囊县| 吉林省| 开封县| 古蔺县| 手游| 蕉岭县| 洪江市| 浦江县| 宁都县| 中卫市| 芦溪县| 镇安县| 旌德县| 香格里拉县| 砚山县| 都昌县| 香港 | 台北市| 修文县| 内黄县| 许昌市| 九寨沟县| 稷山县| 轮台县| 曲松县| 西宁市| 瓮安县|