作者:程式碼鋼琴家-lulipro
出處:http://www.cnblogs.com/lulipro/
連結:https://www.cnblogs.com/lulipro/p/5628750.html
英文原文:http://www.javaranch.com/journal/2002/10/equalhash.html
內容作者翻譯整理,其中加入了自己的話和一些例子,便於理解
前言
在程式設計中,有很多的“公約”,遵守約定去實現你的程式碼,會讓你避開很多坑,這些公約是前人總結出來的設計規範。
Object類是Java中的萬類之祖,其中,equals和hashCode是2個非常重要的方法。
這2個方法總是被人放在一起討論。最近在看集合框架,為了打基礎,就決定把一些細枝末節清理掉。一次性搞清楚!
下麵開始剖析。
public boolean equals(Object obj)
Object類中預設的實現方式是 : return this == obj 。那就是說,只有this 和 obj取用同一個物件,才會傳回true。
而我們往往需要用equals來判斷 2個物件是否等價,而非驗證他們的唯一性。這樣我們在實現自己的類時,就要重寫equals.
按照約定,equals要滿足以下規則。
自反性: x.equals(x) 一定是true
對null: x.equals(null) 一定是false
對稱性: x.equals(y) 和 y.equals(x)結果一致
傳遞性: a 和 b equals , b 和 c equals,那麼 a 和 c也一定equals。
一致性: 在某個執行時期間,2個物件的狀態的改變不會不影響equals的決策結果,那麼,在這個執行時期間,無論呼叫多少次equals,都傳回相同的結果。
一個例子
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
//能執行到這裡,說明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編寫指導
Test類物件有2個欄位,num和data,這2個欄位代表了物件的狀態,他們也用在equals方法中作為評判的依據。
在第8行,傳入的比較物件的取用和this做比較,這樣做是為了 save time ,節約執行時間,如果this 和 obj是 對同一個堆物件的取用,那麼,他們一定是qeuals 的。
接著,判斷obj是不是為null,如果為null,一定不equals,因為既然當前物件this能呼叫equals方法,那麼它一定不是null,非null 和 null當然不等價。
然後,比較2個物件的執行時類,是否為同一個類。不是同一個類,則不equals。getClass傳回的是 this 和obj的執行時類的取用。如果他們屬於同一個類,則傳回的是同一個執行時類的取用。註意,一個類也是一個物件。
1、有些程式員使用下麵的第二種寫法替代第一種比較執行時類的寫法。應該避免這樣做。
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
if(!(obj instanceof Test))
return false; // avoid 避免!
它違反了公約中的對稱原則。
例如:假設Dog擴充套件了Aminal類。
dog instanceof Animal 得到true
animal instanceof Dog 得到false
這就會導致
animal.equls(dog) 傳回true
dog.equals(animal) 傳回false
僅當Test類沒有子類的時候,這樣做才能保證是正確的。
2、按照第一種方法實現,那麼equals只能比較同一個類的物件,不同類物件永遠是false。但這並不是強制要求的。一般我們也很少需要在不同的類之間使用equals。
3、在具體比較物件的欄位的時候,對於基本值型別的欄位,直接用 == 來比較(註意浮點數的比較,這是一個坑)對於取用型別的欄位,你可以呼叫他們的equals,當然,你也需要處理欄位為null 的情況。對於浮點數的比較,我在看Arrays.binarySearch的原始碼時,發現瞭如下對於浮點數的比較的技巧:
if ( Double.doubleToLongBits(d1) == Double.doubleToLongBits(d2) ) //d1 和 d2 是double型別
if( Float.floatToIntBits(f1) == Float.floatToIntBits(f2) ) //f1 和 f2 是d2是float型別
4、並不總是要將物件的所有欄位來作為equals 的評判依據,那取決於你的業務要求。比如你要做一個家電功率統計系統,如果2個家電的功率一樣,那就有足夠的依據認為這2個家電物件等價了,至少在你這個業務邏輯背景下是等價的,並不關心他們的價錢啊,品牌啊,大小等其他引數。
5、最後需要註意的是,equals 方法的引數型別是Object,不要寫錯!
public int hashCode()
這個方法傳回物件的雜湊碼,傳回值是int型別的雜湊碼。
物件的雜湊碼是為了更好的支援基於雜湊機制的Java集合類,例如 Hashtable, HashMap, HashSet 等。
關於hashCode方法,一致的約定是:
重寫了euqls方法的物件必須同時重寫hashCode()方法。
如果2個物件透過equals呼叫後傳回是true,那麼這個2個物件的hashCode方法也必須傳回同樣的int型雜湊碼
如果2個物件透過equals傳回false,他們的hashCode傳回的值允許相同。(然而,程式員必須意識到,hashCode傳回獨一無二的雜湊碼,會讓儲存這個物件的hashtables更好地工作。)
在上面的例子中,Test類物件有2個欄位,num和data,這2個欄位代表了物件的狀態,他們也用在equals方法中作為評判的依據。那麼, 在hashCode方法中,這2個欄位也要參與hash值的運算,作為hash運算的中間引數。這點很關鍵,這是為了遵守:2個物件equals,那麼 hashCode一定相同規則。
也是說,參與equals函式的欄位,也必須都參與hashCode 的計算。
合乎情理的是:同一個類中的不同物件傳回不同的雜湊碼。典型的方式就是根據物件的地址來轉換為此物件的雜湊碼,但是這種方式對於Java來說並不是唯一的要求的
的實現方式。通常也不是最好的實現方式。
相比 於 equals公認實現約定,hashCode的公約要求是很容易理解的。有2個重點是hashCode方法必須遵守的。約定的第3點,其實就是第2點的
細化,下麵我們就來看看對hashCode方法的一致約定要求。
第一:在某個執行時期間,只要物件的(欄位的)變化不會影響equals方法的決策結果,那麼,在這個期間,無論呼叫多少次hashCode,都必須傳回同一個雜湊碼。
第二:透過equals呼叫傳回true 的2個物件的hashCode一定一樣。
第三:透過equasl傳回false 的2個物件的雜湊碼不需要不同,也就是他們的hashCode方法的傳回值允許出現相同的情況。
總結一句話:等價的(呼叫equals傳回true)物件必須產生相同的雜湊碼。不等價的物件,不要求產生的雜湊碼不相同。
hashCode編寫指導
在編寫hashCode時,你需要考慮的是,最終的hash是個int值,而不能上限溢位。不同的物件的hash碼應該儘量不同,避免hash衝突。
那麼如果做到呢?下麵是解決方案。
1、定義一個int型別的變數 hash,初始化為 7。
接下來讓你認為重要的欄位(equals中衡量相等的欄位)參入雜湊運,算每一個重要欄位都會產生一個hash分量,為最終的hash值做出貢獻(影響)
重要欄位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()) |
最後把所有的分量都總和起來,註意並不是簡單的相加。選擇一個倍乘的數字31,參與計算。然後不斷地遞迴計算,直到所有的欄位都參與了。
int hash = 7;
hash = 31 * hash + 欄位1貢獻分量;
hash = 31 * hash + 欄位2貢獻分量;
.....
return hash;
說明,以下的內容是我在google上找到並翻譯整理的,其中加入了自己的話和一些例子,便於理解,但我能保證這並不影響整體準確性。
●編號802,輸入編號直達本文
●輸入m獲取文章目錄
資料庫開發
更多推薦《18個技術類微信公眾號》
涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。