<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    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 閱讀(348) 評論(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比較,看你愿意.
    比較不是說不能比較,文章說得很清楚,如果要求的同一認定那就不應該用不同類型進行相同性比較.  回復  更多評論
      

    主站蜘蛛池模板: 成人伊人亚洲人综合网站222| 国产亚洲综合久久| 久久青草免费91观看| 国产成人精品亚洲精品| 日韩精品视频在线观看免费| 四虎影视免费永久在线观看| 国产成人不卡亚洲精品91| 小小影视日本动漫观看免费| 亚洲欧美日韩综合俺去了| 国产国产人免费人成免费视频| 亚洲精品无码av中文字幕| 日本免费电影一区| 国产精品亚洲精品久久精品 | 免费少妇a级毛片人成网| 激情无码亚洲一区二区三区 | 最新欧洲大片免费在线 | 亚洲国产美女在线观看| 国产香蕉免费精品视频| 亚洲色成人网站WWW永久四虎| 大学生一级特黄的免费大片视频 | 丁香亚洲综合五月天婷婷| 国产乱妇高清无乱码免费| 亚洲AV综合色一区二区三区| 久久A级毛片免费观看| 国产亚洲精品成人AA片| 国产又黄又爽又刺激的免费网址| 四虎国产精品成人免费久久 | 一级做a爰片久久毛片免费看| 亚洲AV无码乱码在线观看富二代| 亚洲电影在线免费观看| 亚洲三级在线观看| 亚洲一级黄色视频| 一二三四影视在线看片免费 | 免费A级毛片av无码| 亚洲国产成人久久精品大牛影视| 亚洲国产精品人人做人人爱| 久久国产精品免费专区| 亚洲精华国产精华精华液好用| 亚洲性在线看高清h片| 免费一本色道久久一区| 国产99精品一区二区三区免费|