Java里的equals總結

jopen 9年前發布 | 13K 次閱讀 Java Java開發

前段時間一直在工作中使用 Java,由于有一些C++功底,于是簡單看了一下Java相關的語法便開始編寫代碼,結果在創建一個自定義類,并將自定義類放入ArrayList中,之后查找ArrayList是否有此元素的時候,發現怎么也查詢不到對應的元素。在網上搜了一下資料,發現原因是沒有重寫對象的equals()方法,導致無法查找到對應的對象。之后由查了與之聯系的相關資料,便有了以下的總結。

這篇總結的形式是提出個問題,然后給出問題的答案。這是目前學習知識的一種嘗試,可以讓學習更有目的。

Q1.什么時候應當重寫對象的equals方法?

答:一般在我們需要進行值比較的時候,是需要重寫對象的equals方法的。而例外情況在《effective java》的第7條“在改寫equals的時候請遵守通用約定”中清楚描述了。

我們知道,在Java中,每個對象都繼承于Object.如果不重寫,則默認的equals代碼如下所示:

public boolean euqals(Object obj){
    return this == obj;
}

由上面的代碼可以看出,equal默認是使用“==”來判斷兩個對象是否相等。兩個對象使用“==”比較的是對象的地址,只有兩個引用指向的對象相同的時候,“==”才返回true。所以,在開頭的例子中,就需要重寫equals方法,讓兩個對象有equals的時候。

Q2.如何重寫equals?

答:首先,當改寫equals方法時,需要保證滿足它的通用約定。這些約定如下所示:

  • 自反性,對于任意的引用值x,x.equals(x)一定為true。
  • 對稱性,對于任意的引用值x和y,當且僅當y.equals(x)時,x.equals(y)也一定返回true.
  • 傳遞性,對于任意的引用值x,y,z。如果x.equals(y)返回true,y.euqals(z)返回true,則x.equals(z)也返回true。
  • 一致性,對于任意的引用值x和y,如果用于equals比較的對象信息沒有修改,那么,多次調用x.equals(y)要么一致返回true,要么一致返回false.
  • 非空性,所有的對象都必須不等于null。
  • </ul>

    其實我覺的一個簡單的方法是參照String的equals方法即可,官方出版,滿足各種要求。其代碼如下所示

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = count;
            if (n == anotherString.count) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = offset;
                int j = anotherString.offset;
                while (n– != 0) {
                    if (v1[i++] != v2[j++])
                        return false;
                }
                return true;
            }
        }
        return false;
    }

    函數的解釋如下所示:

    1. 使用==檢查“實參是否是指向對象的一個引用”。
    2. 使用instanceof檢查實參是否和本對象同類,如果不同類,就不相等。
    3. 將實參轉換為正確的類型。
    4. 根據類的定義,檢查實現此對象值相等的各個條件。
    5. </ol>

      更詳細的信息,還是請看《effective java》的第7條“在改寫equals的時候請遵守通用約定”。

      Q3.修改equals時需要注意什么?

      答:大致需要注意以下幾點:

      若修改equals方法,也請修改hashCode方法

      首先這個是語言的一個約定,這么做的一個原因是當此對象作為哈希容器的元素時,需要依賴hashCode,對象默認的hashCode是返回一個此對象特有的hashCode,不同的對象的hashCode返回值是不一樣的,而哈希容器處理元素時,是按照對象的哈希值將對象分配到不同的桶中,若我們不重寫對象的hashCode,那么值相等的對象產生的哈希值也會不同,這樣當在哈希容器中查找時,會找不到對應的元素。

      更詳細的信息請看《effective Java》的第8條“改寫equals時總是要改寫hashCode”。

      重寫時保證函數聲明的正確
      請注意equals的聲明是

      public boolean equals(Object obj)

      參數類型是Object,如果參數類型是此對象類型的話,如下:

      class Point{
      final int x;
      final int y;
      public void Point(int x, int y)
          this.x = x;
          this.y = y;
      }
      public boolean euqals(Point obj){
               return (this.x == obj.x && this.y == obj.y);
          }
      }

      下面代碼執行是按照我們的預期執行的。

      Point a(1, 2);
      Poinr b(1, 2);
      System.out.println(a.equals(b));// 輸出true

      但是如果將類A放入容器中,則會出問題

      import java.util.HashSet;

      HashSet<Point> coll = new HashSet<Point>(); coll.add(a); System.out.println(coll.contains(b));// 輸出false</pre>

      這是由于HashSet中的contains方法中調用的是equals(Object obj),而Point中的equals(Object obj)仍是Object的equals,這個方法在前面已經說過了,比較的是對象的地址,所以在coll中調用contains(b)時,當然得不到true。

      當有繼承關系時注意equals的正確
      當一個類重寫equals方法后,另一個類繼承此類,此時,可能會違反前面說到的對稱性,代碼如下所示:

      public class ColoredPoint extends Point { 
          private final Color color;
          public ColoredPoint(int x, int y, Color color) {
              super(x, y);
              this.color = color;
          }

      @Override 
      public boolean equals(Object other) {
          boolean result = false;
          if (other instanceof ColoredPoint) {
              ColoredPoint that = (ColoredPoint) other;
              result = (this.color.equals(that.color) && super.equals(that));
          }
          return result;
      }
      

      }</pre>

      當我們作比較時

      Point p = new Point(1, 2);
      ColoredPoint cp = new ColoredPoint(1, 2, Color.RED);
      System.out.println(p.equals(cp)); //輸出ture
      System.out.println(cp.equals(p)); //輸出false

      原因是當調用Point.equals的時候,只比較了Point的x和y坐標,同時ColoredPoint也是Point類型,所以上面第三行代碼相等,而調用ColoredPoint的時候,Point不是ColoredPoint類型,這樣就導致第四行代碼輸出false。

      若我們忽略Color的信息來比較呢,例如將ColoredPoint的equals方法改為:

      @overwrite
      public boolean equals(Object obj){
          if((obj instanceof Point)){
              return false;
          }

      if(!(obj instanceof ColoredPoint)){
          return obj.equals(this);
      }
      
      return super.equals(obj) && ((ColoredPoint)obj).color == color;
      

      }</pre>

      這樣就保證了對稱性,但是卻違反了傳遞性,即下面的情況:

      ColoredPoint cp1 = new ColoredPoint(1, 2, Color.RED);
      Point p = new Point(1, 2);
      ColoredPoint cp2 = new ColoredPoint(1, 2, Color.BLUE);
      System.out.println(cp1.equals(p)); //true
      System.out.println(p.equals(cp2)); //true
      System.out.println(cp1.equals(cp2)); //false

      面對這種情況,大致有兩種解決方案,一種酷殼的文章--如何在Java中避免equals方法的隱藏陷阱的最后一條,斷絕了Point和ColoredPoint相等的可能,這是一種處理方法,認為Point和ColoredPoint是不同的。另一種方法是effective Java上提出的,使用聚合而不是繼承,將Point作為ColoredPoint的一個成員變量。目前我傾向于這種方法,因為聚合比繼承更靈活,耦合更低。這種方法的代碼如下所示:

      class ColoredPoint{
          private final Point point;
          private final Color color;

      public Point asPoint(){
          return point;
      }
      
      public boolean equals(Object obj){
          boolean ret = false;
          if(obj instanceof ColoredPoint){
              ColoredPoint that = (ColoredPoint)obj;
              ret = that.point.equals(point) && color.equals(that.color);
          }
          return ret;
      }
      

      }</pre>

      當ColoredPoint需要比較坐標時,可以調用asPoint方法來轉化為坐標進行比較。其他情況比較坐標和顏色,這樣就可以解決上面關于對稱性和傳遞性的問題了。

      以上就是全文的內容,由于水平有限,文章中難免會有錯誤,希望大家指正。謝謝

      [1/30]

      參考資料:

      1. effective Java
      2. 如何在Java中避免equals方法的隱藏陷阱
      3. Java中equals()與hashCode()方法詳解
      4. </ol> </div> 來自:http://www.cnblogs.com/yetuweiba/p/4314421.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!