Kimi's NutShell

          我荒廢的今日,正是昨日殞身之人祈求的明日

          BlogJava 新隨筆 管理
            141 Posts :: 0 Stories :: 75 Comments :: 0 Trackbacks
          深入equals方法

          時間:2006-07-26
          作者:axman
          瀏覽次數: 2070
          本文關鍵字:java,?equal,?基礎
          文章工具
          推薦給朋友?推薦給朋友
          打印文章?打印文章

            equals方法的重要性毋須多言,只要你想比較的兩個對象不愿是同一對象,你就應該實現equals方法,讓對象用你認為相等的條件來進行比較。

            下面的內容只是API的規范,沒有什么太高深的意義,但我之所以最先把它列在這兒,是因為這些規范在事實中并不是真正能保證得到實現。

          1. 對于任何引用類型, o.equals(o) == true成立。
          2. 如果 o.equals(o1) == true 成立,那么o1.equals(o)==true也一定要成立。
          3. 如果 o.equals(o1) == true 成立且? o.equals(o2) == true 成立,那么o1.equals(o2) == true 也成立。
          4. 如果第一次調用o.equals(o1) == true成立再o和o1沒有改變的情況下以后的任何次調用都成立。
          5. o.equals(null) == true 任何時間都不成立。

            以上幾條規則并不是最完整的表述,詳細的請參見API文檔。

            對于Object類,它提供了一個最最嚴密的實現,那就是只有是同一對象是,equals方法才返回true,也就是人們常說的引用比較而不是值比較。這個實現嚴密得已經沒有什么實際的意義,所以在具體子類(相對于Object來說)中,如果我們要進行對象的值比較,就必須實現自己的equals方法。

            先來看一下以下這段程序:

              public boolean equals(Object obj)
              {
                  if (obj == null) return false;
                  if (!(obj instanceof FieldPosition))
                      return false;
                  FieldPosition other = (FieldPosition) obj;
                  if (attribute == null) {
                      if (other.attribute != null) {
                          return false;
                      }
                  }
                  else if (!attribute.equals(other.attribute)) {
                      return false;
                  }
                  return (beginIndex == other.beginIndex
                      && endIndex == other.endIndex
                      && field == other.field);
              }
          

            這是JDK中java.text.FieldPosition的標準實現,似乎沒有什么可說的.?我相信大多數或絕大多數程序員認為,這是正確的合法的equals實現.畢竟它是JDK的API實現啊.?還是讓我們以事實來說話吧:

          package debug;
          import java.text.*;
          public class Test {
            public static void main(String[] args) {
              FieldPosition fp = new FieldPosition(10);
              FieldPosition fp1 = new MyTest(10);
              System.out.println(fp.equals(fp1));
              System.out.println(fp1.equals(fp));
            }
          }
          class MyTest extends FieldPosition{
            int x = 10;
            public MyTest(int x){
              super(x);
              this.x = x;
            }
            public boolean equals(Object o){
              if(o==null) return false;
              if(!(o instanceof MyTest )) return false;
              return ((MyTest)o).x == this.x;
            }
          }
          

             運行一下看看會打印出什么:

          System.out.println(fp.equals(fp1));打印true
          System.out.println(fp1.equals(fp));打印flase

            兩個對象,出現了不對稱的equals算法.問題出在哪里(腦筋急轉彎:當然出在JDK實現的BUG)?

            我相信有太多的程序員(除了那些根本不知道實現equals方法的程序員外)在實現equals方法時都用過instanceof運行符來進行短路優化的,實事求是地說很長一段時間我也這么用過。太多的教程,文檔都給了我們這樣的誤導。而有些稍有了解的程序員可能知道這樣的優化可能有些不對但找不出問題的關鍵。另外一種極端是知道這個技術缺陷的骨灰級專家就提議不要這樣應用。

            我們知道,"通常"要對兩個對象進行比較,那么它們"應該"是同一類型。所以首先利用nstanceof運行符進行短路優化,如果被比較的對象不和當前對象是同一類型則不用比較返回false,但事實上,"子類是父類的一個實例",所以如果 子類 o instanceof 父類,始終返回true,這時肯定不會發生短路優化,下面的比較有可能出現多種情況,一種是不能造型成子類而拋出異常,另一種是父類的private 成員沒有被子類繼承而不能進行比較,還有就是形成上面這種不對稱比較。可能會出現太多的情況。

            那么,是不是就不能用 instanceof運行符來進行優化?答案是否定的,JDK中仍然有很多實現是正確的,如果一個class是final的,明知它不可能有子類,為什么不用 instanceof來優化呢?

            為了維護SUN的開發小組的聲譽,我不說明哪個類中,但有一個小組成員在用這個方法優化時在后加加上了加上了這樣的注釋:

           if (this == obj)             // quick check
                   return true;
                if (!(obj instanceof XXXXClass))  // (1) same object?
                   return false;

            可能是有些疑問,但不知道如何做(不知道為什么沒有打電話給我......)

            那么對于非final類,如何進行類型的quick check呢?

          if(obj.getClass() != XXXClass.class) return false;

            用被比較對象的class對象和當前對象的class比較,看起來是沒有問題,但是,如果這個類的子類沒有重新實現equals方法,那么子類在比較的時候,obj.getClass() 肯定不等于XXXCalss.class,也就是子類的equals將無效,所以if(obj.getClass() != this.getClass()) return false;才是正確的比較。
          另外一個quick check是if(this==obj) return true;

            是否equals方法一定比較的兩個對象就一定是要同一類型?上面我用了"通常",這也是絕大多數程序員的愿望,但是有些特殊的情況,我們可以進行不同類型的比較,這并不違反規范。但這種特殊情況是非常罕見的,一個不恰當的例子是,Integer類的equals可以和Sort做比較,比較它們的value是不是同一數學值。(事實上JDK的API中并沒有這樣做,所以我才說是不恰當的例子)。在完成quick check以后,我們就要真正實現你認為的“相等”。對于如果實現對象相等,沒有太高的要求,比如你自己實現的“人”類,你可以認為只要name相同即認為它們是相等的,其它的sex,ago都可以不考慮。這是不完全實現,但是如果是完全實現,即要求所有的屬性都是相同的,那么如何實現equals方法?

          class Human{
           private String name;
           private int ago;
           private String sex;
                  ....................
                  public boolean equals(Object obj){
            quick check.......
            Human other = (Human)ojb;
            return this.name.equals(other.name) 
             && this.ago == ohter.ago
             && this.sex.equals(other.sex);
           }
          }
          

             這是一個完全實現,但是,有時equals實現是在父類中實現,而要求被子類繼承后equals能正確的工作,這時你并不事實知道子類到底擴展了哪些屬性,所以用上面的方法無法使equals得到完全實現。

            一個好的方法是利用反射來對equals進行完全實現:

           public boolean equals(Object obj){
            quick check.......
            Class c = this.getClass();
            Filed[] fds = c.getDeclaredFields();
            for(Filed f:fds){
             if(!f.get(this).equals(f.get(obj)))
              return false;
            }
            return true;
           }
          

            為了說明的方便,上明的實現省略了異常,這樣的實現放在父類中,可以保證你的子類的equals可以按你的愿望正確地工作。

            關于equals方法的最后一點是:如果你要是自己重寫(正確說應該是履蓋)了equals方法,那同時就一定要重寫hashCode().為是規范,否則.............

            我們還是看一下這個例子:

          public final class PhoneNumber {
              private final int areaCode;
              private final int exchange;
              private final int extension;
              public PhoneNumber(int areaCode, int exchange, int extension) {
                  rangeCheck(areaCode, 999, "area code");
                  rangeCheck(exchange, 99999999, "exchange");
                  rangeCheck(extension, 9999, "extension");
                  this.areaCode = areaCode;
                  this.exchange = exchange;
                  this.extension = extension;
              }
              private static void rangeCheck(int arg, int max, String name) {
                  if(arg < 0 || arg > max)
                      throw new IllegalArgumentException(name + ": " + arg);
              }
              public boolean equals(Object o) {
                  if(o == this)
                      return true;
                  if(!(o instanceof PhoneNumber))
                      return false;
                  PhoneNumber pn = (PhoneNumber)o;
                  return pn.extension == extension && pn.exchange == exchange && pn.areaCode == areaCode;
              }
          }
          

            注意這個類是final的,所以這個equals實現沒有什么問題。

            我們來測試一下:

              public static void main(String[] args) {
                  Map hm = new HashMap();
                  PhoneNumber pn = new PhoneNumber(123, 38942, 230);
                  hm.put(pn, "I love you");
                  PhoneNumber pn1 = new PhoneNumber(123, 38942, 230);
                  System.out.println(pn);
                  System.out.println("pn.equals(pn1) is " + pn.equals(pn1));
                  System.out.println(hm.get(pn1));
                  System.out.println(hm.get(pn));
              }
          

            既然pn.equals(pn1),那么我put(pn,"I love you");后,get(pn1)這什么是null呢?

            答案是因為它們的hashCode不一樣,而hashMap就是以hashCode為主鍵的。

            所以規范要求,如果兩個對象進行equals比較時如果返回true,那么它們的hashcode要求返回相等的值。

            好了,休息,休息一下。。。。。。。。。。。。。。。。

            轉載自dev2dev網友axman的go deep into java專欄

          個人自述

          一個男人.
          一個寫程序的男人.
          一個寫程序并正在從程序中尋找快樂的男人.
          一個寫程序并正在從程序中尋找快樂并把快樂傳遞給大家的男人.

          一個書生.
          一個寂寞的書生.
          一個寂寞的梅香竹影下敲聲寫韻的書生.
          一個寂寞的梅香竹影下敲聲寫韻晨鐘暮鼓中逸氣揚劍的書生.

          那個男人是位書生。沒有人知道他的姓名,居無定所,行無定蹤,亦耕變讀,或漁或樵。
          所以有人叫他樵夫(Axman),有人叫他漁郎(fisher)。

          posted on 2007-02-02 17:00 Kimi 閱讀(349) 評論(2)  編輯  收藏 所屬分類: Java

          評論

          # re: 精讀:深入equal() 方法 (轉載:Axman)[未登錄] 2007-11-05 11:46 teasp
          這個Axman過于自大了。子類的對象為什么就不能和父類的對象equals?lsp原則忘記了嗎?  回復  更多評論
            

          # re: 精讀:深入equal() 方法 (轉載:Axman) 2008-02-26 16:24 stone li
          樓上真SB,人家只是說子類和父類比較會出現什么樣的結果.誰說不能比較了?
          Axman還把Integer和Sort比較,看你愿意.
          比較不是說不能比較,文章說得很清楚,如果要求的同一認定那就不應該用不同類型進行相同性比較.  回復  更多評論
            

          主站蜘蛛池模板: 五原县| 泽普县| 厦门市| 高阳县| 栾川县| 惠安县| 宁安市| 兴国县| 青浦区| 沁水县| 平乡县| 五华县| 勃利县| 大名县| 仙桃市| 上高县| 洛南县| 普兰店市| 宾川县| 铁岭市| 平潭县| 防城港市| 承德市| 遂宁市| 化隆| 鞍山市| 南丰县| 方城县| 柘荣县| 茌平县| 庐江县| 桦南县| 滦南县| 南投县| 承德县| 富宁县| 清新县| 江山市| 南涧| 平武县| 苍南县|