(轉)hashCode()與equals()

          有效和正確定義hashCode() 和equals()
          每個Java對象都有hashCode()和 equals()方法。許多類忽略(Override)這些方法的缺省實施,
          以在對象實例之間提供更深層次的語義可比性。在Java理念和實踐這一部分,Java開發人
          員Brian Goetz向您介紹在創建Java類以有效和準確定義hashCode()和equals()時應遵循的
          規則和指南。您可以在討論論壇與作者和其它讀者一同探討您對本文的看法。(您還可以點擊
          本文頂部或底部的討論進入論壇。)
          雖然Java語言不直接支持關聯數組 -- 可以使用任何對象作為一個索引的數組 -- 但在根
          Object類中使用hashCode()方法明確表示期望廣泛使用HashMap(及其前輩Hashtable)。理想
          情況下基于散列的容器提供有效插入和有效檢索;直接在對象模式中支持散列可以促進基
          于散列的容器的開發和使用。
          定義對象的相等性
          Object類有兩種方法來推斷對象的標識:equals()和hashCode()。一般來說,如果您忽略了其
          中一種,您必須同時忽略這兩種,因為兩者之間有必須維持的至關重要的關系。特殊情況是
          根據equals() 方法,如果兩個對象是相等的,它們必須有相同的hashCode()值(盡管這通常
          不是真的) 。
          特定類的equals()的語義在Implementer的左側定義;定義對特定類來說equals()意味著什么
          是其設計工作的一部分。Object 提供的缺省實施簡單引用下面等式:
          public boolean equals(Object obj) { return (this == obj); }
          在這種缺省實施情況下,只有它們引用真正同一個對象時這兩個引用才是相等的。同樣,
          Object提供的hashCode()的缺省實施通過將對象的內存地址對映于一個整數值來生成。由于
          在某些架構上,地址空間大于int值的范圍,兩個不同的對象有相同的hashCode()是可能的。
          如果您忽略了hashCode(),您仍舊可以使用System.identityHashCode()方法來接入這類缺省
          值。
          忽略 equals() -- 簡單實例
          缺省情況下,equals()和hashCode()基于標識的實施是合理的,但對于某些類來說,它們希
          望放寬等式的定義。例如, Integer 類定義equals() 與下面類似:
          public boolean equals(Object obj) {
          return (obj instanceof Integer
          && intValue() == ((Integer) obj).intValue());
          }
          在這個定義中,只有在包含相同的整數值的情況下這兩個Integer對象是相等的。結合將不
          可修改的Integer,這使得使用Integer作為HashMap中的關鍵字是切實可行的。這種基于值
          的Equal 方法可以由Java 類庫中的所有原始封裝類使用,如Integer、Float、Character 和
          Boolean以及String(如果兩個String對象包含相同順序的字符,那它們是相等的)。由于這些
          類都是不可修改的并且可以實施hashCode()和equals(),它們都可以做為很好的散列關鍵字。
          為什么忽略 equals() 和hashCode()?
          如果Integer不忽略equals() 和 hashCode()情況又將如何?如果我們從未在HashMap或其它
          基于散列的集合中使用Integer 作為關鍵字的話,什么也不會發生。但是,如果我們在
          HashMap中使用這類Integer對象作為關鍵字,我們將不能夠可靠地檢索相關的值,除非我
          們在get()調用中使用與put()調用中極其類似的Integer實例。這要求確保在我們的整個程序
          中,只能使用對應于特定整數值的Integer對象的一個實例。不用說,這種方法極不方便而
          且錯誤頻頻。
          Object的interface contract要求如果根據 equals()兩個對象是相等的,那么它們必須有相同
          的hashCode()值。當其識別能力整個包含在equals()中時,為什么我們的根對象類需要
          hashCode()?hashCode()方法純粹用于提高效率。Java平臺設計人員預計到了典型Java應用
          程序中基于散列的集合類(Collection Class)的重要性--如Hashtable、HashMap和HashSet,
          并且使用equals()與許多對象進行比較在計算方面非常昂貴。使所有Java對象都能夠支持
          hashCode() 并結合使用基于散列的集合, 可以實現有效的存儲和檢索。
          實施equals() 和hashCode() 的需求
          實施equals()和 hashCode()有一些限制,Object文件中列舉出了這些限制。特別是equals()方
          法必須顯示以下屬性:
          Symmetry : 兩個引用, a 和 b,a.equals(b) if and only if b.equals(a)
          Reflexivity : 所有非空引用, a.equals(a)
          Transitivity : If a.equals(b) and b.equals(c), then a.equals(c)
          Consistency with hashCode() : 兩個相等的對象必須有相同的hashCode() 值
          Object的規范中并沒有明確要求equals()和 hashCode() 必須一致 -- 它們的結果在隨后的調
          用中將是相同的,假設“不改變對象相等性比較中使用的任何信息。”這聽起來象“計算的
          結果將不改變,除非實際情況如此。”這一模糊聲明通常解釋為相等性和散列值計算應是對
          象的可確定性功能, 而不是其它。
          對象相等性意味著什么?
          人們很容易滿足Object 類規范對equals() 和 hashCode() 的要求。決定是否和如何忽略
          equals()除了判斷以外,還要求其它。在簡單的不可修值類中,如Integer(事實上是幾乎所有
          不可修改的類),選擇相當明顯 -- 相等性應基于基本對象狀態的相等性。在Integer情況下,
          對象的唯一狀態是基本的整數值。
          對于可修改對象來說,答案并不總是如此清楚。equals() 和hashCode() 是否應基于對象的標
          識(象缺省實施)或對象的狀態(象Integer和String)?沒有簡單的答案 -- 它取決于類的計劃
          使用。對于象List和Map這樣的容器來說,人們對此爭論不已。Java類庫中的大多數類,包
          括容器類, 錯誤出現在根據對象狀態來提供equals() 和hashCode() 實施。
          如果對象的hashCode()值可以基于其狀態進行更改,那么當使用這類對象作為基于散列的
          集合中的關鍵字時我們必須注意,確保當它們用于作為散列關鍵字時,我們并不允許更改
          它們的狀態。所有基于散列的集合假設,當對象的散列值用于作為集合中的關鍵字時它不會
          改變。如果當關鍵字在集合中時它的散列代碼被更改,那么將產生一些不可預測和容易混淆
          的結果。實踐過程中這通常不是問題 -- 我們并不經常使用象List這樣的可修改對象做為
          HashMap 中的關鍵字。
          一個簡單的可修改類的例子是Point,它根據狀態來定義equals()和hashCode()。如果兩個
          Point 對象引用相同的(x, y)座標,Point的散列值來源于x和y座標值的IEEE 754-bit表示,
          那么它們是相等的。
          對于比較復雜的類來說,equals()和hashCode()的行為可能甚至受到superclass或interface的
          影響。例如,List接口要求如果并且只有另一個對象是List,而且它們有相同順序的相同的
          Elements(由Element上的Object.equals() 定義),List對象等于另一個對象。hashCode()的需
          求更特殊--list 的hashCode() 值必須符合以下計算:
          hashCode = 1;
          Iterator i = list.iterator();
          while (i.hasNext()) {
          Object obj = i.next();
          hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
          }
          不僅僅散列值取決于list的內容,而且還規定了結合各個Element的散列值的特殊算法。
          (String 類規定類似的算法用于計算String 的散列值。)
          編寫自己的equals() 和hashCode() 方法
          忽略缺省的equals()方法比較簡單,但如果不違反對稱(Symmetry)或傳遞性
          (Transitivity)需求,忽略已經忽略的equals() 方法極其棘手。當忽略equals()時,您應該總
          是在equals()中包括一些Javadoc 注釋,以幫助那些希望能夠正確擴展您的類的用戶。
          作為一個簡單的例子, 考慮以下類:
          class A {
          final B someNonNullField;
          C someOtherField;
          int someNonStateField;
          }
          我們應如何編寫該類的equals() 的方法? 這種方法適用于許多情況:
          public boolean equals(Object other) {
          // Not strictly necessary, but often a good optimization
          if (this == other)
          return true;
          if (!(other instanceof A))
          return false;
          A otherA = (A) other;
          return
          (someNonNullField.equals(otherA.someNonNullField))
          && ((someOtherField == null)
          ? otherA.someOtherField == null
          : someOtherField.equals(otherA.someOtherField)));
          }
          現在我們定義了equals(),我們必須以統一的方法來定義hashCode()。一種統一但并不總是
          有效的定義hashCode() 的方法如下:
          public int hashCode() { return 0; }
          這種方法將生成大量的條目并顯著降低HashMaps的性能,但它符合規范。一個更合理的
          hashCode() 實施應該是這樣:
          public int hashCode() {
          int hash = 1;
          hash = hash * 31 + someNonNullField.hashCode();
          hash = hash * 31
          + (someOtherField == null ? 0 : someOtherField.hashCode());
          return hash;
          }
          注意:這兩種實施都降低了類狀態字段的equals()或hashCode()方法一定比例的計算能力。
          根據您使用的類,您可能希望降低superclass的equals()或hashCode()功能一部分計算能力。
          對于原始字段來說,在相關的封裝類中有helper 功能,可以幫助創建散列值,如
          Float.floatToIntBits 。
          編寫一個完美的equals()方法是不現實的。通常,當擴展一個自身忽略了equals()的
          instantiable類時,忽略equals()是不切實際的,而且編寫將被忽略的equals()方法(如在抽象
          類中)不同于為具體類編寫equals()方法。關于實例以及說明的更詳細信息請參閱Effective
          Java Programming Language Guide, Item 7 ( 參考資料) 。
          有待改進?
          將散列法構建到Java類庫的根對象類中是一種非常明智的設計折衷方法 -- 它使使用基于
          散列的容器變得如此簡單和高效。但是,人們對Java類庫中的散列算法和對象相等性的方
          法和實施提出了許多批評。java.util中基于散列的容器非常方便和簡便易用,但可能不適用
          于需要非常高性能的應用程序。雖然其中大部分將不會改變,但當您設計嚴重依賴于基于散
          列的容器效率的應用程序時必須考慮這些因素, 它們包括:
          太小的散列范圍。使用int而不是long作為hashCode()的返回類型增加了散列沖突的幾率。
          糟糕的散列值分配。短strings和小型integers的散列值是它們自己的小整數,接近于其它“
          鄰近”對象的散列值。一個循規導矩(Well-behaved)的散列函數將在該散列范圍內更均勻
          地分配散列值。
          無定義的散列操作。雖然某些類,如String和List,定義了將其Element的散列值結合到一
          個散列值中使用的散列算法,但語言規范不定義將多個對象的散列值結合到新散列值中的
          任何批準的方法。我們在前面編寫自己的equals()和hashCode()方法中討論的List、String或
          實例類A使用的訣竅都很簡單,但算術上還遠遠不夠完美。類庫不提供任何散列算法的方
          便實施, 它可以簡化更先進的hashCode() 實施的創建。
          當擴展已經忽略了equals()的 instantiable類時很難編寫equals()。當擴展已經忽略了equals()
          的 instantiable類時,定義equals()的“顯而易見的”方式都不能滿足equals()方法的對稱或
          傳遞性需求。這意味著當忽略equals()時,您必須了解您正在擴展的類的結構和實施詳細信
          息,甚至需要暴露基本類中的機密字段,它違反了面向對象的設計的原則。
          結束語
          通過統一定義equals()和hashCode(),您可以提升類作為基于散列的集合中的關鍵字的使用
          性。有兩種方法來定義對象的相等性和散列值:基于標識,它是Object提供的缺省方法;
          基于狀態,它要求忽略equals()和hashCode()。當對象的狀態更改時如果對象的散列值發生
          變化,確信當狀態作為散列關鍵字使用時您不允許更更改其狀態。

          posted on 2007-07-25 20:38 hugh 閱讀(437) 評論(0)  編輯  收藏 所屬分類: JAVA

          <2007年7月>
          24252627282930
          1234567
          891011121314
          15161718192021
          22232425262728
          2930311234

          導航

          統計

          公告

          小弟打算把硬盤里的所有資料整理到博客里,但是其中收藏了不少網上的文章(個人也記不住作者),請作者見諒!請知道作者的朋友(或作者本人)看到了請告訴小弟,小弟好把作者加上!

          常用鏈接

          留言簿(2)

          隨筆分類

          隨筆檔案

          收藏夾

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 平安县| 宝坻区| 闽清县| 遂宁市| 浦东新区| 甘南县| 贺州市| 西乌| 东山县| 沈丘县| 剑河县| 中卫市| 张家港市| 襄城县| 开封县| 进贤县| 德州市| 新郑市| 周宁县| 武清区| 南宁市| 沈丘县| 满城县| 黔西县| 龙口市| 宜兰县| 绥江县| 偏关县| 慈利县| 临汾市| 天门市| 高台县| 陆川县| 耿马| 余江县| 安阳县| 年辖:市辖区| 高台县| 加查县| 宁城县| 鸡东县|