我的漫漫程序之旅

          專注于JavaWeb開發(fā)
          隨筆 - 39, 文章 - 310, 評論 - 411, 引用 - 0
                以上幾條規(guī)則并不是最完整的表述,詳細的請參見API文檔.對于Object類,它提供了一個最最嚴密的實現(xiàn),那就是只有是同一對象時,equals方法才返回true,也就是人們常說的引用比較而不是值比較.這個實現(xiàn)嚴密得已經(jīng)沒有什么實際的意義, 所以在具體子類(相對于Object來說)中,如果我們要進行對象的值比較,就必須實現(xiàn)自己的equals方法.先來看一下以下這段程序:

             
           public boolean equals(Object obj)
              
          {
                  
          if (obj == nullreturn 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==nullreturn 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算法.問題出在哪里(腦筋急轉彎:當然出在JDK實現(xiàn)的BUG)?我相信有太多的程序員(除了那些根本不知道實現(xiàn)equals方法的程序員外)在實現(xiàn)equals方法時都用過instanceof運行符來進行短路優(yōu)化的,實事求是地說很長一段時間我也這么用過。

               太多的教程,文檔都給了我們這樣的誤導。而有些稍有了解的程序員可能知道這樣的優(yōu)化可能有些不對但找不出問題的關鍵。另外一種極端是知道這個技術缺陷的骨灰級專家就提議不要這樣應用。我們知道,"通常"要對兩個對象進行比較,那么它們"應該"是同一類型。所以首先利用instanceof運算符進行短路優(yōu)化,如果被比較的對象不和當前對象是同一類型則不用比較返回false。

              但事實上,"子類是父類的一個實例",所以如果子類 o instanceof 父類,始終返回true,這時肯定不會發(fā)生短路優(yōu)化,接下來的比較(即子類與父類比較)有可能出現(xiàn)多種情況,一種是不能造型成父類而拋出異常,另一種是父類的private 成員沒有被子類繼承而不能進行比較,還有就是形成上面這種不對稱比較。可能會出現(xiàn)太多的情況。

              那么,是不是就不能用 instanceof運算符來進行優(yōu)化?答案是否定的,JDK中仍然有很多實現(xiàn)是正確的,如果一個class是final的,明知它不可能有子類,為什么不用 instanceof來優(yōu)化呢?為了維護SUN的開發(fā)小組的聲譽,我不說明哪個類中,但有一個小組成員在用這個方法優(yōu)化時加上了這樣的注釋:
           

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

                      return false;



               可能是有些疑問,但不知道如何做(不知道為什么沒有打電話給我......)那么對于非final類,如何進行類型的quick check呢?

          if(obj.getClass() != XXXClass.classreturn false;



               用被比較對象的class對象和當前對象的class比較,看起來是沒有問題,但是,如果這個類的子類沒有重新實現(xiàn)equals方法,那么子類在比較的時候,obj.getClass() 肯定不等于XXXCalss.class, 也就是子類的equals將無效(兩個子類對象比較總是false),所以

          if(obj.getClass() != this.getClass()) return false;



          才是正確的比較。另外一個quick check是if(this==obj) return true;

              是否equals方法比較的兩個對象一定是要同一類型?上面我用了"通常",這也是絕大多數(shù)程序員的愿望,但是有些特殊的情況,我們可以進行不同類型的比較,這并不違反規(guī)范。但這種特殊情況是非常罕見的,一個不恰當?shù)睦邮牵琁nteger類的equals可以和Sort做比較,比較它們的value是不是同一數(shù)學值。(事實上JDK的API中并沒有這樣做,所以我才說是不恰當?shù)睦樱┰谕瓿蓂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得到完全實現(xiàn)。
          一個好的方法是利用反射來對equals進行完全實現(xiàn):  

           

          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;
           }


               為了說明的方便,上明的實現(xiàn)省略了異常,這樣的實現(xiàn)放在父類中,可以保證你的子類的equals可以按你的愿望正確地工作。關于equals方法的最后一點是:如果你要是自己重寫(正確說應該是履蓋)了equals方法,那同時就一定要重寫hashCode().這是規(guī)范,否則.............

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

          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(12338942230
          );
                  hm.put(pn, 
          "I love you"
          );
                  PhoneNumber pn1 
          = new PhoneNumber(12338942230
          );
                  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為主鍵的。所以規(guī)范要求,如果兩個對象進行equals比較時如果返回true,那么它們的hashcode要求返回相等的值。



          數(shù)據(jù)加載中……

          深入研究equals方法

           equals方法的重要性毋須多言,只要你想比較兩個對象是不是同一對象,你就應該實現(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 成立且  o.equals(o2) == true 成立,那么
            o1.equals(o2) == true 也成立.

          4.如果第一次調(diào)用o.equals(o1) == true成立,在o和o1沒有改變的情況下以后的任何次調(diào)用都成立.

          5.o.equals(null) == true 任何時間都不成立.

          posted on 2008-01-05 16:21 々上善若水々 閱讀(470) 評論(0)  編輯  收藏 所屬分類: J2SE

          主站蜘蛛池模板: 绥宁县| 循化| 密云县| 金塔县| 丹阳市| 维西| 汾阳市| 东乡县| 台前县| 钟山县| 遵义县| 乐平市| 通化县| 卢龙县| 谢通门县| 军事| 福泉市| 珠海市| 望奎县| 马关县| 乌兰察布市| 文登市| 南和县| 罗田县| 天津市| 开化县| 佛坪县| 阿拉善左旗| 洛扎县| 肇州县| 武城县| 湘阴县| 自治县| 乐亭县| 灵寿县| 朔州市| 迁安市| 嘉义市| 扶绥县| 安阳县| 汝南县|