John Jiang

          a cup of Java, cheers!
          https://github.com/johnshajiang/blog

             :: 首頁 ::  :: 聯(lián)系 :: 聚合  :: 管理 ::
            131 隨筆 :: 1 文章 :: 530 評(píng)論 :: 0 Trackbacks
          "一致性相等"的陷阱
          關(guān)于Object類中的equals()方法與Comparable接口中的compareTo()方法之間有何種關(guān)聯(lián),之前還真沒考慮過。通過java.net看到此文之后,收獲了一點(diǎn)兒新知識(shí),希望大家也能如此。(2012.12.09最后更新)

              方法equals()與Comparable接口中的compareTo()方法是Java中最基本的兩個(gè)方法之一,然而它們的定義卻圍繞著"與相等一致"這一有趣的概念。

          equals()方法

              Java中的equals()方法既明確,又模糊。Java清楚地定義了如何準(zhǔn)確地檢驗(yàn)一個(gè)equals()方法是可用的。一個(gè)恰當(dāng)?shù)膃quals()方法必須是自反的,對(duì)稱的,可傳遞的,一致的,并能處理null引用。
              然而equals()方法又是不清晰的。Javadoc說到,該方法指定了其它對(duì)象是"等于"這個(gè)對(duì)象的。注意,"等于"是放在引號(hào)中的。此處的關(guān)鍵就是,它沒有定義如何去判定這種相等性。
          • 對(duì)象的一致性(==)默認(rèn)是繼承自O(shè)bject類
          • 對(duì)象的整體可觀測(cè)的狀態(tài),例如,若兩個(gè)對(duì)象是相等的,那么在應(yīng)用的其它部分可以用一個(gè)對(duì)象去替代另一個(gè)對(duì)象。
          • 對(duì)象信息中的某些部分,如ID,使得檢驗(yàn)對(duì)象相等性在邏輯上是有意義的。
          compareTo()方法
               Comparable接口定義了可比較性的概念。Javadoc指出compareTo()方法"強(qiáng)制設(shè)定了每個(gè)實(shí)現(xiàn)了該接口的類的對(duì)象的全部順序"。
               實(shí)現(xiàn)了Comparable接口的類有一個(gè)天然的排序,這可便于存儲(chǔ),也能在不使用單獨(dú)的Comparator的情況下,用于像TreeSet和TreeMap這樣的集合對(duì)象。
               該接口的定義明晰,它要求其實(shí)現(xiàn)必須確保對(duì)稱性與傳遞性,就像equals()方法那樣。

          一致性/非一致性相等
          Comparable接口有如下描述
              類C的天然排序意味著要與equals()方法保持一致,只有當(dāng)且僅當(dāng)e1.compareTo(e2) == 0與e1.equals(e2)有相同的布爾值。
              基本上,這就要求由compareTo()定義的相等性與equals()方法定義的相等性具有相同的概念(除去有null的情況)。乍一看,該要求很簡(jiǎn)單,但實(shí)際上它有其復(fù)雜性,后面將會(huì)討論到。
              當(dāng)考慮到操作符重載時(shí),這種定義就特別有用。若我們假設(shè)有一種類Java語言,在這種語言中,==并不表示對(duì)象的同一性,而是通過方法去進(jìn)行比較,大于/小于操作符也是如此,問題是調(diào)什么樣的方法。在類Java語言中大于/小于天然地就要基于compareTo()方法,而==則要調(diào)用equals()方法。
          // our new Java-like language
          if (a < b) return "Less";      // translation ignoring nulls: if (a.compareTo(b) < 0)
          if (a > b) return "Greater";   // translation ignoring nulls: if (a.compareTo(b) > 0)
          if (a == b) return "Equal";    // translation ignoring nulls: if (a.equals(b))
          throw new Exception("Impossible assuming no nulls?");
              但如果compareTo()方法不是"一致性相等",那么上述代碼將會(huì)拋出異常,因?yàn)楫?dāng)a.equals(b)為false時(shí),a.compareTo(b)會(huì)返回0。
              在集合,如TreeMap,中還會(huì)發(fā)生其它問題:
          // Foo class is "inconsistent with equals"
          assert foo1.equals(foo2) == false;
          assert foo1.compareTo(foo2) == 0;
           
          TreeMap
          <Foo, String> map = 
          map.put(foo1, 
          "a");
          map.put(foo2, 
          "b");
              當(dāng)使用equals()方法時(shí),這兩個(gè)對(duì)象不相等,但使用compareTo()時(shí),它們卻相等。在這種情況下,該Map的元素個(gè)數(shù)將為1,而非0。
              由于這些"一致性相等"的問題,Javadoc說道"強(qiáng)烈建議(盡管并不要求)天然排序規(guī)則要與equals()方法保持一致"。
              JDK中的許多類為了符合"一致性相等"這一規(guī)范而實(shí)現(xiàn)了Comparable接口。這些類包括Byte,Short,Integer,Long,Character和String。

          還有些更有趣的類:
              BigDecimal--肯定是"非一致性相等",比如4.00與4.0不一致,但進(jìn)行比較時(shí),認(rèn)為它們是一樣的。
              Double/Float--該類顯式地提供了排序規(guī)則,并為正零和負(fù)零,以及NaN都提供了相等性檢查,以確保它的compareTo()方法符合"一致性相等"。
              CharSet--該類基于ID或名稱。equals()方法對(duì)待字段串是大小寫敏感的,但compareTo()方法卻不這樣。雖然名稱一般會(huì)符合某種標(biāo)準(zhǔn),但這是一種值得懷疑的"一致性"。
              *Buffer(nio)--該簇類的比較基于緩沖存放的內(nèi)容,在我的測(cè)試中equals()和compareTo()是"一致的"。
              Rdn(ldap)--該類的比較基于狀態(tài)的標(biāo)準(zhǔn)化格式,因此也是"一致性相等"。
              ObjectStreamField(序列化)--該類的比較基于名稱,但會(huì)首先對(duì)基本數(shù)據(jù)類型進(jìn)行排序。因?yàn)闆]有覆蓋equals()方法,所以是"非一致性相等"。
              ...
              注意:對(duì)于大多數(shù)的例子,我都不得不查看其源代碼或編寫測(cè)試程序以確定該類是不是符合"一致性相等"。這兒有一個(gè)不錯(cuò)地清理Javadoc和檢驗(yàn)UUID equals()方法的Adopt-a-JDK任務(wù)。

          JSR-310
              一直看到許多關(guān)于BigDecimal的問題,已有計(jì)劃將帖子顯示這將造成多么大的爭(zhēng)議。
              基本上,為某些類定義equals()和compareTo()看起來很容易。LocalDate表示某單一日歷系統(tǒng)中的某個(gè)日期,所以它有一個(gè)顯而易見的排序算法和相等規(guī)則。LocalTime則表示某個(gè)時(shí)刻,所以它也有一個(gè)明顯的排序算法和相等規(guī)則。Instant表示時(shí)間線上的某個(gè)時(shí)刻,那么它的排序與相等也是顯見的。
              但在其它的情況下,這就不是那么顯而易見了。考慮這樣一個(gè)類OffsetDateTime:
          dt1 = OffsetDateTime.parse("2012-11-05T06:00+01:00");
          dt2 
          = OffsetDateTime.parse("2012-11-05T07:00+02:00");
              這樣的兩個(gè)日期-時(shí)刻對(duì)象代表時(shí)間線上一個(gè)相同的時(shí)刻點(diǎn),但它們有不同的本地時(shí),而且相對(duì)的UTC/格林威治時(shí)間的偏移量也不相同。
              那么就有一個(gè)問題要留給讀者們...你更傾向于如下哪種觀點(diǎn)...
              1. dt1不等于dt2,compareTo()分別比較本地時(shí)與偏移量,使用"一致性相等"(使用獨(dú)立的Comparator基于時(shí)刻對(duì)其進(jìn)行排序)。
              2. dt1不等于dt2,compareTo()基于時(shí)間線的上時(shí)刻點(diǎn),使用"非一致性相等"。
              3. dt1等于dt2,compareTo()基于時(shí)間線的上時(shí)刻點(diǎn),使用"一致性相等"。
              4. dt1等于dt2,且不實(shí)現(xiàn)Comparable接口。
              5. dt1不等于dt2,且不實(shí)現(xiàn)Comparable接口。
              我個(gè)人更傾向于讓dt1.equals(dt2)返回true這種方案,但我仍持開放態(tài)度。
              順便地,也可以將這個(gè)問題提給BigDecimal,如果你能修改這個(gè)類,使其符合"一致性相等",你會(huì)修改它的equals()方法,還是compareTo()方法?

          posted on 2012-12-06 23:14 John Jiang 閱讀(2179) 評(píng)論(3)  編輯  收藏 所屬分類: JavaSE 、Java 、翻譯

          評(píng)論

          # re: "一致性相等"的陷阱(譯) 2012-12-07 16:20 牌具
          受教了。以后不會(huì)掉陷阱了  回復(fù)  更多評(píng)論
            

          # re: "一致性相等"的陷阱(譯) 2012-12-16 15:36 來如風(fēng)
          我的理解,equals、 是為了測(cè)試業(yè)務(wù)相等規(guī)則,比如購物車中兩條物品完全相同,包括名稱,數(shù)量,這就是equals,而compared 則用于業(yè)務(wù)對(duì)象排序,比如按照某個(gè)屬性在內(nèi)存中排序,因?yàn)樘嫠祷厝N結(jié)果  回復(fù)  更多評(píng)論
            

          # re: "一致性相等"的陷阱(譯) 2012-12-17 13:00 Sha Jiang
          @來如風(fēng)
          文章關(guān)注于compareTo()返回0的這種情況  回復(fù)  更多評(píng)論
            

          主站蜘蛛池模板: 哈密市| 建德市| 都昌县| 定日县| 梁平县| 舟山市| 卢龙县| 贞丰县| 革吉县| 三都| 如东县| 石屏县| 苍溪县| 龙胜| 河北区| 星座| 河东区| 赤峰市| 杭锦旗| 原平市| 南涧| 天水市| 麟游县| 长沙县| 泰安市| 鸡西市| 宁陕县| 信宜市| 松江区| 遵义市| 搜索| 慈利县| 静安区| 鲁山县| 离岛区| 达拉特旗| 华坪县| 通化市| 临湘市| 娄底市| 贡嘎县|