Android面試雜記
Java 中的 hashCode 方法返回實例的散列值,這個方法主要是為了哈希表提供的,只要不改變equals方法中的比較信息,每次在同一個實例上調(diào)用hashcode方法,必須始終返回相同的整數(shù)。下一次運行時,整數(shù)不必保持一致。
通過equals 方法比較相等的兩個實例,他們調(diào)用hashcode 得到的整數(shù)必須相同.通過equals 方法比較不相等的兩個實例,他們調(diào)用hashcode 得到的整數(shù),可以相等,但最好是不相同的,因為這有助于提高哈希表的性能。
素數(shù)性質(zhì):31是一個素數(shù),素數(shù)有較好的特性,能夠減少哈希沖突的概率,使得生成的哈希碼分布更加均勻。在哈希表中,較好的分布可以減少鏈表長度,提高查找效率。
性能優(yōu)勢:在計算機中,乘法運算比較高效,尤其是2的冪次方,而31正好是2的冪次方減1(31 = 2^5 - 1),因此對于大多數(shù)的實現(xiàn)來說,乘以31相當(dāng)于進行了移位和減法操作,比乘以其他任意的素數(shù)要快速。
簡單性:使用31作為乘數(shù)是一種簡化的做法。在實踐中證明,它在大多數(shù)情況下都能產(chǎn)生不錯的結(jié)果。 ? ? ?
equals 方法就要重寫 hashcode 方法
hashCode方法本質(zhì)就是一個哈希函數(shù),這個不是我根據(jù)字面意思瞎猜的,而是Object類的作者說明的。Object類的作者在注釋的最后一段的括號中寫道:將對象的地址值映射為integer類型的哈希值。(如果對哈希函數(shù)定義不大理解的同學(xué)可以看我另外一篇文章: )。牢牢把握哈希函數(shù)的定義有利于幫助我們理解接下來的內(nèi)容。
我們看到,hashCode方法注釋中列了個列表,列表中有三條注釋,當(dāng)前需要理解的大致意思如下:
一個對象多次調(diào)用它的hashCode方法,應(yīng)當(dāng)返回相同的integer(哈希值)。
兩個對象如果通過equals方法判定為相等,那么就應(yīng)當(dāng)返回相同integer。
兩個地址值不相等的對象調(diào)用hashCode方法不要求返回不相等的integer,但是要求擁有兩個不相等integer的對象必須是不同對象。
對象與哈希值對應(yīng)關(guān)系
我們看到,圖中存在兩種獨立的情況:
相同的對象必然導(dǎo)致相同的哈希值。
不同的哈希值必然是由不同對象導(dǎo)致的。
也就是作者在hashCode方法注釋上寫明了的定義,實際上作者也就是在實現(xiàn)一個哈希函數(shù),并且把哈希函數(shù)的定義寫到注釋里。
其實我們看到這里,就能明白一件事情了:equals方法與hashCode方法根本就是配套使用的。對于任何一個對象,不論是使用繼承自O(shè)bject的equals方法還是重寫equals方法。hashCode方法實際上必須要完成的一件事情就是,為該equals方法認定為相同的對象返回相同的哈希值。
Object類中的equals方法區(qū)分兩個對象的做法是比較地址值,即使用“==”。而我們?nèi)缛舾鶕?jù)業(yè)務(wù)需求改寫了equals方法的實現(xiàn),那么也應(yīng)當(dāng)同時改寫hashCode方法的實現(xiàn)。否則hashCode方法依然返回的是依據(jù)Object類中的依據(jù)地址值得到的integer哈希值。
這么說起來可能不是很好理解,我們代入到具體的例子--String類中好了。
String類中,equals方法經(jīng)過重寫,具體實現(xiàn)源碼如下:
?public boolean equals(Object anObject) {
? ? ? ?if (this == anObject) {
? ? ? ? ? ?return true;
? ? ? ?}
? ? ? ?if (anObject instanceof String) {
? ? ? ? ? ?String anotherString = (String)anObject;
? ? ? ? ? ?int n = length();
? ? ? ? ? ?if (n == anotherString.length()) {
? ? ? ? ? ? ? ?int i = 0;
? ? ? ? ? ? ? ?while (n-- != 0) {
? ? ? ? ? ? ? ? ? ?if (charAt(i) != anotherString.charAt(i))
? ? ? ? ? ? ? ? ? ? ? ? ? ?return false;
? ? ? ? ? ? ? ? ? ?i++;
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?return true;
? ? ? ? ? ?}
? ? ? ?}
? ? ? ?return false;
? ?}
String類equals方法的重寫實現(xiàn)
通過源碼我們能看到,String對象在調(diào)用equals方法比較另一個對象時,除了認定相同地址值的兩個對象相等以外,還認定對應(yīng)著的每個字符都相等的兩個String對象也相等,即使這兩個String對象的地址值不同(即屬于兩個對象)。
此時我們能想到的是,String類中對equals方法進行重寫擴充了,但是如果此時我們不將hashCode方法也進行重寫,那么String類調(diào)用的就是來自頂級父類Obejct類中的hashCode方法。即,對于兩個字符串對象,使用他們各自的地址值映射為哈希值。也就是會出現(xiàn)如下情形:
創(chuàng)建兩個地址值不同,字面量相同的字符串對象
也就是說,被String類中的equals方法認定為相等的兩個對象擁有兩個不同的哈希值(因為他們的地址值不同)。問題分析到這一步,原來的問題“為什么重寫equals方法就得重寫hashCode方法”已經(jīng)結(jié)束了,它的答案是“因為必須保證重寫后的equals方法認定相同的兩個對象擁有相同的哈希值”。同時我們順便得出了一個結(jié)論:“hashCode方法的重寫原則就是保證equals方法認定為相同的兩個對象擁有相同的哈希值”。
看到這里,我的內(nèi)心甚至沒有一點波瀾--兩個字面量相同的String對象哈希值不同怎么啦!Object類作者給我的叮囑我不遵守又怎么啦!說到現(xiàn)在為止,上方提到的任何東西都沒有對我的實際代碼沒有造成任何影響。實際上是這樣嗎?兩個被認定為相同的對象擁有不同的哈希值沒有造成不便或者bug嗎?這就是我們接下來要進一步挖掘的問題:為什么要保證它們的哈希值相等呢?“hashCode方法返回的哈希值在語言中扮演了一個什么角色?”。
在java集合Map中,如果將equals重寫相等,沒有重寫hashcode,會出現(xiàn)下面情況,
map.put("1",hello) map.put("1",world)? 這樣map中會有2個實例存在,而不是一個。? ?
幾乎可以肯定地說,hashCode方法不僅僅是與equals配套使用的,它甚至是與Java集合配套使用的。同樣地,類似的代碼我們也能在HashTable中找到,就更不用提HashSet一類的集合了。
集合本身在我們?nèi)粘5木幋a中就必不可少,所以我們以后為了代碼不出問題還是乖乖地重寫hashCode方法吧。不過好在一般我們?yōu)榱思系男室约鞍踩裕紩褂貌豢勺兊腟tring,它已經(jīng)將hashCode方法重寫了,并且重寫的是一個散列極為優(yōu)秀的hashCode方法。