
(點(diǎn)擊上方公眾號(hào),可快速關(guān)注)
原文:eclipsesource
譯文:ImportNew - 南半球
鏈接:http://www.importnew.com/16517.html
在 java 中,每一個(gè)對(duì)象都有一個(gè)容易理解但是仍然有時(shí)候被遺忘或者被誤用的 hashCode 方法。這里有3件事情要時(shí)刻牢記以避免常見(jiàn)的陷阱。
一個(gè)對(duì)象的哈希碼允許算法和數(shù)據(jù)結(jié)構(gòu)將對(duì)象放入隔間,就象打印機(jī)類型案件中的字母類型。打印機(jī)將所有的“A”類型放到一個(gè)房間,它尋找這個(gè)“A”的時(shí)候就只需要在這個(gè)房間進(jìn)行尋找。這種簡(jiǎn)單的系統(tǒng)讓他在未排序的抽屜中尋找類型的時(shí)候更快。這也是基于哈希的集合的想法,例如 HashMap 和 HashSet。

為了使你的類與其他基于哈希的集合或其他依賴哈希碼的算法一起正常工作,所有 hashCode 的實(shí)現(xiàn)必須遵守一個(gè)簡(jiǎn)單的契約。
hashCode 契約
這個(gè)契約在 hashCode 方法的 JavaDoc 中進(jìn)行了闡述。它可以大致的歸納為下面幾點(diǎn):
在一個(gè)運(yùn)行的進(jìn)程中,相等的對(duì)象必須要有相同的哈希碼
請(qǐng)注意這并不意味著以下常見(jiàn)的誤解:
不相等的對(duì)象一定有著不同的哈希碼——錯(cuò)!
有同一個(gè)哈希值的對(duì)象一定相等——錯(cuò)!

這個(gè)契約允許不同的對(duì)象共享相同的哈希碼,例如根據(jù)上圖中的的描述,“A”和“μ”對(duì)象的哈希值就一樣。在數(shù)學(xué)術(shù)語(yǔ)中,從對(duì)象到哈希碼的映射不一定為內(nèi)射或者雙射。這是顯而易見(jiàn)的,因?yàn)榭赡艿牟煌瑢?duì)象的數(shù)量經(jīng)常比可能的哈希嗎的數(shù)量 (2^32)更大。
編輯:在早期的版本中,我錯(cuò)誤的認(rèn)為哈希碼的映射一定屬于內(nèi)射,但是不一定是雙射,這顯然是錯(cuò)的。感謝 Lucian 指出這個(gè)錯(cuò)誤。
這個(gè)約定直接導(dǎo)致了第一個(gè)規(guī)則:
1. 無(wú)論你何時(shí)實(shí)現(xiàn) equals 方法,你必須同時(shí)實(shí)現(xiàn) hashCode 方法
如果你不這樣做,你將會(huì)帶來(lái)?yè)p壞的對(duì)象。為什么?一個(gè)對(duì)象的 hashCode 方法需要與 equals 方法考慮同樣的域。通過(guò)重寫(xiě) equals 方法,你將申明一些對(duì)象與其他對(duì)象相等,但是原始的 hashCode 方法將所有的對(duì)象看做是不同的。所以你將會(huì)有不同哈希碼的相同對(duì)象。例如,在 HashMap 中調(diào)用 contains 方法將會(huì)返回 false,即使這個(gè)對(duì)象已經(jīng)被添加。
怎樣寫(xiě)一個(gè)好的 hashCode 方法不在這篇文章的范圍內(nèi),在 Joshua Bloch 很受歡迎的書(shū)《Effective Java》中被很好的闡釋,Java 開(kāi)發(fā)人員的書(shū)架上不應(yīng)缺少這本書(shū)。
【你的項(xiàng)目需要專業(yè)意見(jiàn)嗎?我們的 Developer Support 會(huì)為你解決問(wèn)題。|在我們的 Software Craftsmanship 頁(yè)面上尋找關(guān)于怎樣編寫(xiě)簡(jiǎn)潔代碼的更多提示。】
為了安全起見(jiàn),讓 Eclipse IDE 一次產(chǎn)生 equals 和 hashCode 方法: Source > Generate hashCode() and equals()….

為了保護(hù)你自己,你還可以配置 Eclipse 來(lái)檢測(cè)實(shí)現(xiàn)了 equals 方法但是沒(méi)有實(shí)現(xiàn) hashCode 方法的類,并顯示錯(cuò)誤。不幸的是,此選項(xiàng)默認(rèn)是指為“忽略”:PReferences > Java >Compiler > Errors/Warnings,然后用快速篩選器來(lái)搜索“hashcode”:

更新:正如 laurent 指出,equalsverifier 是一個(gè)強(qiáng)大的工具,它用來(lái)驗(yàn)證 hashCode 和 equals 方法的約定。您應(yīng)該考慮在您的單元測(cè)試中使用它。
哈希碼沖突
任何時(shí)候,兩個(gè)不同對(duì)象有相同的哈希碼,我們稱之為沖突。沖突不要緊,它只是意味著有多個(gè)對(duì)象在同一個(gè)空間里,所以 HashMap 會(huì)再檢查一遍來(lái)找正確的對(duì)象。大量的沖突將會(huì)降低系統(tǒng)的性能,但是它們不會(huì)導(dǎo)致錯(cuò)誤的結(jié)果。
但是如果你誤認(rèn)為哈希碼是一個(gè)對(duì)象唯一的句柄,例如使用它作為Map的key,你有時(shí)會(huì)得到錯(cuò)誤的對(duì)象。因?yàn)殡m然沖突很罕見(jiàn),但他們是不可避免的。例如,字符“Aa”和“BB”產(chǎn)生相同的哈希碼:2112。因此:
2. 永遠(yuǎn)不要把哈希碼誤用作一個(gè)key
你可能會(huì)反對(duì),不像打印機(jī)的類型例子,在 Java 中,有 4,294,967,296 的空間(2^32 個(gè)可能的整型值)。40億的插槽,發(fā)生沖突似乎是極不可能的對(duì)嗎?

事實(shí)證明它不是不太可能。這是令人驚訝的沖突:請(qǐng)想象一下在一個(gè)房間里有 23 個(gè)隨機(jī)的人。你覺(jué)得兩個(gè)人是同一天生日的幾率有多大 ?很低,因?yàn)橐荒暧?365 天嗎?事實(shí)上,幾率是 50% 左右!50 個(gè)人是保守的估計(jì)。這個(gè)現(xiàn)象稱為生日悖論。應(yīng)用到哈希碼,這意味著在 77163 個(gè)不同的對(duì)象中,有 50% 的可能性發(fā)生沖突–假設(shè)你有一個(gè)理想的哈希的函數(shù),均勻地把對(duì)象分布在所有可用的空間里面。
例如:
安然公司的電子郵件集包含 520,924 封電子郵件。計(jì)算電子郵件內(nèi)容字符串的哈希碼時(shí),我發(fā)現(xiàn) 50 對(duì)(甚至是 2 個(gè)三元組)不同的電子郵件有著相同的哈希碼。對(duì)于五十萬(wàn)個(gè)字符串,這是一個(gè)很好的結(jié)果。但是這里的信息是:如果你有很多數(shù)據(jù)元素,沖突就會(huì)發(fā)生。如果你正在使用哈希碼作為 key,你不會(huì)立即注意到你的錯(cuò)誤。但是少數(shù)人會(huì)收到錯(cuò)誤的郵件。
哈希碼可變
最后,在哈希碼的契約中,有一個(gè)很重要的細(xì)節(jié)是相當(dāng)讓人吃驚的:hashCode 并不保證在不同的應(yīng)用執(zhí)行中得到相同的結(jié)果。讓我們看一看 Java 文檔:
在一次 Java 應(yīng)用的執(zhí)行中,對(duì)于同一個(gè)對(duì)象,hashCode 方法必須始終返回相同的整數(shù),但這整數(shù)不反映對(duì)象是否被修改(equals 比較)的信息。同一個(gè)應(yīng)用的不同執(zhí)行,該整數(shù)不必保持一致。
事實(shí)上,這是不常見(jiàn)的,一些類庫(kù)中的類甚至指定它們用于計(jì)算哈希碼的精確公式(例如字符串)。對(duì)于這些類,哈希碼總是會(huì)相同。雖然大部分的哈希碼的實(shí)現(xiàn)提供穩(wěn)定的值,但你不能依賴于這一點(diǎn)。正如這篇文章指出的,有些類庫(kù)在不同進(jìn)程中會(huì)返回不同的哈希值,這有的時(shí)候會(huì)讓人困惑。谷歌的 Protocol Buffers 就是一個(gè)例子。
因此,你不應(yīng)該在分布式應(yīng)用程序中使用哈希碼。一個(gè)遠(yuǎn)程對(duì)象可能與本地對(duì)象有不同的哈希碼,即使這兩個(gè)對(duì)象是相等的。
3. 在分布式應(yīng)用中不要使用哈希碼
此外,你應(yīng)該意識(shí)到從一個(gè)版本到另一個(gè)版本哈希碼的功能實(shí)現(xiàn)可能會(huì)更改。因此您的代碼不應(yīng)該依賴于任何特定的哈希碼值。例如,你不應(yīng)該使用哈希碼來(lái)持久化狀態(tài)。下次你運(yùn)行程序的時(shí)候,“相同”對(duì)象的哈希碼可能不同。
最好的建議可能是:完全不使用哈希碼,除非你自己創(chuàng)造了基于哈希的算法。
一種替代方法:SHA1
你可能知道加密的哈希碼 SHA1 有時(shí)被用來(lái)標(biāo)識(shí)對(duì)象(例如,git這樣做)。這也是不安全嗎?不。SHA1 使用 160 位密鑰,這使得沖突幾乎是不可能的。即使有很多對(duì)象,在這個(gè)空間發(fā)生沖突的幾率遠(yuǎn)遠(yuǎn)低于一顆流星撞到你正在執(zhí)行程序的電腦的幾率。這篇文章對(duì)沖突的概率作了很好的概述。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注