so true

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

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

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

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

          1。自反性:對于任何引用類型, o.equals(o) == true成立。
          2。對稱性:如果 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沒有改變的情況下以后的任何次調(diào)用都成立。
          5。o.equals(null) == true 任何時間都不成立。
            以上幾條規(guī)則并不是最完整的表述,詳細的請參見API文檔。
              但是在hashCode()函數(shù)的協(xié)定中,要求“如果根據(jù) equals(Object) 方法,兩個對象是相等的,那么對這兩個對象中的每個對象調(diào)用 hashCode 方法都必須生成相同的整數(shù)結(jié)果。”,因此我個人認為equals方法還應(yīng)該加上這第6條規(guī)定:
          6。如果 o.equals(o1) == true 成立,那么必須有o.hashCode==o1.hashCode()。

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

            先來看一下以下這段引自于JDK中的程序:
          (說明: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的標準實現(xiàn),似乎沒有什么可說的. 我相信大多數(shù)或絕大多數(shù)程序員認為,這是正確的合法的equals實現(xiàn).畢竟它是JDK的API實現(xiàn)啊. 還是讓我們以事實來說話吧:

          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  
            兩個對象,出現(xiàn)了不對稱的equals算法.問題出在哪里(腦筋急轉(zhuǎn)彎:當然出在JDK實現(xiàn)的固有BUG,看來JDK中的東西未必就完全都是最最最正確的,我們要敢于懷疑它!)?

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

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

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

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

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

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

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

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

            一個好的方法是利用反射來對equals進行完全實現(xiàn): 給出一個完整的在父類中定義的equals方法(只是比較字段)的實現(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;
          }
            為了說明的方便,上明的實現(xiàn)省略了異常,這樣的實現(xiàn)放在父類中,可以保證你的子類的equals可以按你的愿望正確地工作。當然這里只是考慮了基于字段相等的比較,如何還涉及到基于方法等其他方面的相等比較,則應(yīng)該擴展上面的代碼,反射出更多的東西(比如方法)來加以比較判斷。

            關(guān)于equals方法的最后一點是:如果你要是自己重寫(正確說應(yīng)該是履蓋)了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實現(xiàn)沒有什么問題。

            我們來測試一下:

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

            答案是因為pn和pn1的hashCode不一樣,而hashMap就是以hashCode為主鍵的。

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

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

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

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

          主站蜘蛛池模板: 通江县| 敦煌市| 独山县| 广安市| 泸西县| 寻甸| 大连市| 望城县| 疏勒县| 双柏县| 泰兴市| 上栗县| 镇赉县| 巴里| 丰台区| 吴堡县| 射阳县| 天柱县| 昌平区| 长岛县| 叶城县| 瓦房店市| 理塘县| 景谷| 卢湾区| 岚皋县| 永昌县| 瓦房店市| 金溪县| 鲁山县| 淅川县| 灵川县| 宜丰县| 东乡县| 射阳县| 河北区| 清远市| 鹤岗市| 曲麻莱县| 丰顺县| 泰兴市|