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

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

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

    so true

    心懷未來(lái),開(kāi)創(chuàng)未來(lái)!
    隨筆 - 160, 文章 - 0, 評(píng)論 - 40, 引用 - 0
    數(shù)據(jù)加載中……

    [改編]深入equals方法,討論instanceof的使用!

    equals方法的重要性毋須多言,只要你想比較的兩個(gè)對(duì)象不僅僅局限在只是同一對(duì)象(即它們的引用相同),你就應(yīng)該實(shí)現(xiàn)equals方法,讓對(duì)象用你認(rèn)為相等的條件來(lái)進(jìn)行比較。

      下面的內(nèi)容只是API的規(guī)范,沒(méi)有什么太高深的意義,但我之所以最先把它列在這兒,是因?yàn)檫@些規(guī)范在事實(shí)中并不是真正能保證得到實(shí)現(xiàn)。

    1。自反性:對(duì)于任何引用類型, o.equals(o) == true成立。
    2。對(duì)稱性:如果 o.equals(o1) == true 成立,那么o1.equals(o)==true也一定要成立。
    3。傳遞性:如果 o.equals(o1) == true 成立且  o1.equals(o2) == true 成立,那么o.equals(o2) == true 也成立。
    4。一致性:如果第一次調(diào)用o.equals(o1) == true成立再o和o1沒(méi)有改變的情況下以后的任何次調(diào)用都成立。
    5。o.equals(null) == true 任何時(shí)間都不成立。
      以上幾條規(guī)則并不是最完整的表述,詳細(xì)的請(qǐng)參見(jiàn)API文檔。
        但是在hashCode()函數(shù)的協(xié)定中,要求“如果根據(jù) equals(Object) 方法,兩個(gè)對(duì)象是相等的,那么對(duì)這兩個(gè)對(duì)象中的每個(gè)對(duì)象調(diào)用 hashCode 方法都必須生成相同的整數(shù)結(jié)果。”,因此我個(gè)人認(rèn)為equals方法還應(yīng)該加上這第6條規(guī)定:
    6。如果 o.equals(o1) == true 成立,那么必須有o.hashCode==o1.hashCode()。

      對(duì)于Object類,它提供了一個(gè)最最嚴(yán)密的實(shí)現(xiàn),那就是只有是同一對(duì)象是,equals方法才返回true,也就是人們常說(shuō)的引用比較而不是值比較。這個(gè)實(shí)現(xiàn)嚴(yán)密得已經(jīng)沒(méi)有什么實(shí)際的意義,所以在具體子類(相對(duì)于Object來(lái)說(shuō))中,如果我們要進(jìn)行對(duì)象的值比較,就必須實(shí)現(xiàn)自己的equals方法。

      先來(lái)看一下以下這段引自于JDK中的程序:
    (說(shuō)明:attribute在FieldPosition中有這樣的定義:private Format.Field attribute;)
        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的標(biāo)準(zhǔn)實(shí)現(xiàn),似乎沒(méi)有什么可說(shuō)的. 我相信大多數(shù)或絕大多數(shù)程序員認(rèn)為,這是正確的合法的equals實(shí)現(xiàn).畢竟它是JDK的API實(shí)現(xiàn)啊. 還是讓我們以事實(shí)來(lái)說(shuō)話吧:

    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;
      }
    }
       運(yùn)行一下看看會(huì)打印出什么:

    System.out.println(fp.equals(fp1));打印true
    System.out.println(fp1.equals(fp));打印flase  
      兩個(gè)對(duì)象,出現(xiàn)了不對(duì)稱的equals算法.問(wèn)題出在哪里(腦筋急轉(zhuǎn)彎:當(dāng)然出在JDK實(shí)現(xiàn)的固有BUG,看來(lái)JDK中的東西未必就完全都是最最最正確的,我們要敢于懷疑它!)?

      我相信有太多的程序員在實(shí)現(xiàn)equals方法時(shí)都用過(guò)instanceof運(yùn)行符來(lái)進(jìn)行短路優(yōu)化的,實(shí)事求是地說(shuō)很長(zhǎng)一段時(shí)間我也這么用過(guò)。太多的教程,文檔都給了我們這樣的誤導(dǎo)。而有些稍有了解的程序員可能知道這樣的優(yōu)化可能有些不對(duì)但找不出問(wèn)題的關(guān)鍵。另外一種極端是知道這個(gè)技術(shù)缺陷的骨灰級(jí)專家就提議不要這樣應(yīng)用。

      我們知道,"通常"要對(duì)兩個(gè)對(duì)象進(jìn)行比較,那么它們"應(yīng)該"是同一類型。所以首先利用nstanceof運(yùn)行符進(jìn)行短路優(yōu)化(“短路”《Short-circuiting》,而短路的最大好處就是能夠優(yōu)化性能。所謂的短路就是當(dāng)?shù)谝粋€(gè)被運(yùn)算的表達(dá)式的結(jié)果已經(jīng)能夠決定運(yùn)算的最終結(jié)果時(shí),就不會(huì)再去計(jì)算其他的表達(dá)式,因此可以避免掉額外且不必要的運(yùn)算操作,尤其是當(dāng)所略過(guò)的是復(fù)雜或含有過(guò)程調(diào)用的表達(dá)式時(shí),短路的性能提升幅度更為明顯。比如我們?cè)趇f中同時(shí)用&&判斷多個(gè)條件的時(shí)候,遇到第一個(gè)false的時(shí)候就不會(huì)再計(jì)算后面的表達(dá)式是否為true了),如果被比較的對(duì)象不和當(dāng)前對(duì)象是同一類型則不用比較返回false,但事實(shí)上,"子類是父類的一個(gè)實(shí)例",所以如果 子類 instanceof 父類,始終返回true,這時(shí)肯定不會(huì)發(fā)生短路優(yōu)化,下面的比較有可能出現(xiàn)多種情況,一種是父類不能造型成子類而拋出異常(比如子類.equals(父類)后,在equals內(nèi)部會(huì)進(jìn)行試圖將父類向下造型成子類的操作),另一種是父類的private 成員沒(méi)有被子類繼承而不能進(jìn)行比較,還有就是形成上面這種不對(duì)稱比較。可能會(huì)出現(xiàn)太多的情況。

      那么,是不是就不能用 instanceof運(yùn)行符來(lái)進(jìn)行優(yōu)化?答案是否定的,JDK中仍然有很多實(shí)現(xiàn)是正確的,如果一個(gè)class是final的,明知它不可能有子類,為什么不用instanceof來(lái)優(yōu)化呢?(可見(jiàn)對(duì)于不涉及子類的問(wèn)題時(shí),用instanceof進(jìn)行短路優(yōu)化還是很可行的)

      為了維護(hù)SUN的開(kāi)發(fā)小組的聲譽(yù),我不說(shuō)明哪個(gè)類中,但有一個(gè)小組成員在用這個(gè)方法優(yōu)化時(shí)在后加加上了加上了這樣的注釋:

    if (this == obj)             // quick check(也就是短路優(yōu)化)
             return true;
          if (!(obj instanceof XXXXClass))  // (1) same object?
             return false;  可能是有些疑問(wèn),但不知道如何做(不知道為什么沒(méi)有打電話給我......)

      那么對(duì)于非final類,如何進(jìn)行類型的quick check呢?

    if(obj.getClass() != XXXClass.class) return false;
      用被比較對(duì)象的class對(duì)象和當(dāng)前對(duì)象的class比較,看起來(lái)是沒(méi)有問(wèn)題,但是,如果這個(gè)類的子類沒(méi)有重新實(shí)現(xiàn)equals方法,那么當(dāng)子類.equals(子類)比較時(shí),也就是要使用“if(obj.getClass() != XXXClass.class) return false;”來(lái)進(jìn)行比較了,顯然obj.getClass()《這個(gè)是字類》 肯定不等于XXXCalss.class《這個(gè)是父類》,從這里就直接返回得到false的結(jié)果了,因此子類中定義的equals實(shí)際上根本沒(méi)發(fā)揮出應(yīng)有的效力來(lái),所以if(obj.getClass() != this.getClass()) return false;才是正確的比較。 呵呵,看到了沒(méi)?我們首先否定了instanceof,接下來(lái)又否定了“obj.getClass() != XXXClass.class”這種錯(cuò)誤的比較做法,最后才給出了正確的比較方法。
    另外一個(gè)quick check是if(this==obj) return true;而且這個(gè)短路優(yōu)化應(yīng)該放在“obj.getClass() != XXXClass.class”優(yōu)化的前面。

      是否equals方法一定比較的兩個(gè)對(duì)象就一定是要同一類型?上面我用了"通常",這也是絕大多數(shù)程序員的愿望,但是有些特殊的情況,我們可以進(jìn)行不同類型的比較,這并不違反規(guī)范。但這種特殊情況是非常罕見(jiàn)的,一個(gè)不恰當(dāng)?shù)睦邮牵琁nteger類的equals可以和Sort做比較,比較它們的value是不是同一數(shù)學(xué)值。(事實(shí)上JDK的API中并沒(méi)有這樣做,所以我才說(shuō)是不恰當(dāng)?shù)睦樱T谕瓿蓂uick check以后,我們就要真正實(shí)現(xiàn)你認(rèn)為的“相等”。對(duì)于如果實(shí)現(xiàn)對(duì)象相等,沒(méi)有太高的要求,比如你自己實(shí)現(xiàn)的“人”類,你可以認(rèn)為只要name相同即認(rèn)為它們是相等的,其它的sex,ago都可以不考慮。這是不完全實(shí)現(xiàn),但是如果是完全實(shí)現(xiàn),即要求所有的屬性都是相同的,那么如何實(shí)現(xiàn)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);
    }
    }
       這是一個(gè)完全實(shí)現(xiàn),但是,有時(shí)equals實(shí)現(xiàn)是在父類中實(shí)現(xiàn)的,而且要求被子類繼承后,父類的equals能正確的工作,這時(shí)父類中的equals并不事實(shí)知道子類到底擴(kuò)展了哪些屬性,所以用上面的方法無(wú)法使equals得到完全實(shí)現(xiàn)。

      一個(gè)好的方法是利用反射來(lái)對(duì)equals進(jìn)行完全實(shí)現(xiàn): 給出一個(gè)完整的在父類中定義的equals方法(只是比較字段)的實(shí)現(xiàn):
    public boolean equals(Object obj){
      if(obj == this)
          return true;
      if(obj.getClass() != this.getClass())
          return false;
      Class c = this.getClass();
      Filed[] fds = c.getDeclaredFields();
      for(Filed f:fds){
       if(!f.get(this).equals(f.get(obj)))
        return false;
      }
      return true;
    }
      為了說(shuō)明的方便,上明的實(shí)現(xiàn)省略了異常,這樣的實(shí)現(xiàn)放在父類中,可以保證你的子類的equals可以按你的愿望正確地工作。當(dāng)然這里只是考慮了基于字段相等的比較,如何還涉及到基于方法等其他方面的相等比較,則應(yīng)該擴(kuò)展上面的代碼,反射出更多的東西(比如方法)來(lái)加以比較判斷。

      關(guān)于equals方法的最后一點(diǎn)是:如果你要是自己重寫(正確說(shuō)應(yīng)該是履蓋)了equals方法,那同時(shí)就一定要重寫hashCode(),否則.............

      我們還是看一下這個(gè)例子:

    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;
        }
    }
      注意這個(gè)類是final的,所以這個(gè)equals實(shí)現(xiàn)沒(méi)有什么問(wèn)題。

      我們來(lái)測(cè)試一下:

        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呢?

      答案是因?yàn)閜n和pn1的hashCode不一樣,而hashMap就是以hashCode為主鍵的。

      所以根據(jù)規(guī)范要求(在文章最開(kāi)始部分提到的標(biāo)準(zhǔn)equals方法需要滿足的6條性質(zhì)),如果兩個(gè)對(duì)象進(jìn)行equals比較時(shí)如果返回true,那么它們的hashcode要求返回相等的值。

    此外,在寫hashCode函數(shù)時(shí),需要注意:
    隨便你怎么寫,只要保證A.equals(B)一定可以推出A.hashCode()==B.hashCode()就行;當(dāng)然,應(yīng)該盡量使得A.equals(B)==false的時(shí)候,使得A.hashCode()盡量不等于B.hashCode(),絕對(duì)不會(huì)相等是不可能的,但是要盡量降低概率。

    比如:
    public int hashCode() {
            int result;
            result = getName().hashCode();
            result = 29 * result + getBirthday().hashCode();
            return result;
        }
    其時(shí)就是需要你根據(jù)自己的類給出特有的一種算法,能夠滿足通過(guò)該算法得到的這個(gè)整數(shù)值(也就是hashCode)能夠區(qū)別該類的各個(gè)實(shí)例。

    posted on 2007-12-20 23:19 so true 閱讀(1242) 評(píng)論(0)  編輯  收藏 所屬分類: Java

    主站蜘蛛池模板: 99久久国产精品免费一区二区| 四虎国产精品成人免费久久 | 免费在线人人电影网| 一级免费黄色毛片| 美女视频黄a视频全免费网站色窝 美女被cao网站免费看在线看 | 亚洲看片无码在线视频| 阿v视频免费在线观看| 日韩av无码免费播放| 亚洲中文字幕无码不卡电影| 亚洲国产成人久久综合一区| 久久精品国产亚洲AV电影网| 高清永久免费观看| 久久受www免费人成_看片中文| 亚洲国产一级在线观看| 亚洲精品国产手机| 日韩免费在线中文字幕| 亚洲成a人片在线观看国产| 亚洲国产精品综合久久网各 | 亚洲精品成a人在线观看夫| 国产久爱免费精品视频 | 久别的草原电视剧免费观看| 国产福利免费在线观看| 亚洲邪恶天堂影院在线观看| 日韩在线观看视频免费| 亚洲人成在线播放网站| 香港经典a毛片免费观看看| 亚洲成AⅤ人影院在线观看| WWW免费视频在线观看播放| 亚洲欧洲日产国产综合网| 亚洲香蕉免费有线视频| 亚洲国产美女精品久久久久∴| 亚洲AV无码一区二区三区牲色| 亚欧人成精品免费观看| 亚洲精品成人网站在线观看| 99re免费99re在线视频手机版| 亚洲综合伊人久久综合| 亚洲毛片免费视频| 国产AV无码专区亚洲AV蜜芽| 国产亚洲人成网站在线观看不卡| 蜜臀AV免费一区二区三区| 黄色一级视频免费观看|