java對象equals方法的重寫

又在javaEye發現了一篇好文章,迫切轉過來呵呵

http://www.javaeye.com/topic/269601

 

什麼時候需要重寫equals()?
  我們知道每一個java類都繼承自Object類,equals()是Object類中提供的方法之一。那麼,讓我們先來看看Object#equals()在Java中的原代碼:

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

可以看出,只有當一個實例等於它本身的時候,equals()纔會返回true值。通俗地說,此時比較的是兩個引用是否指向內存中的同一個對象,也可以稱做是否實例相等。而我們在使用equals()來比較兩個指向值對象的引用的時候,往往希望知道它們邏輯上是否相等,而不是它們是否指向同一個對象。在這樣的情況下, 如果超類也沒有重寫equals()以實現期望的行爲,這時我們就需要重寫equals方法。而且這樣做也使得這個類的實例可以被用做映射表(map)的鍵,或者集合(set)的元素,並使映射表或者集合表現出預期的行爲。
Object類提供的equals方法只是一個很簡單的,不能適應應用程序有特殊要求的情況。
  比如網絡對象,帶有volatile屬性的對象,或是帶有多層子對象的複合對象,等等,是不能像String一類的對象進行簡單比較的,所以提供了這樣一個機制,就像serializable接口一樣,既有默認的序列化方法,也提供了程序自己定製,覆蓋默認方式的可能性。
  Object僅僅提供了一個對引用的比較,如果兩個引用不是同一個那就返回false,這是無法滿足大多數對象比較的需要的,所以要覆蓋。

怎樣重寫equals()方法?
  重寫equals()方法看起來非常簡單,但是有許多改寫的方式會導致錯誤,並且後果非常嚴重。要想正確改寫equals()方法,你必須要遵守它的通用約定。下面是約定的內容,來自java.lang.Object的規範:
equals方法實現了等價關係(equivalence relation):
1. 自反性:對於任意的引用值x,x.equals(x)一定爲true。
2. 對稱性:對於任意的引用值x 和 y,當x.equals(y)返回true時,
  y.equals(x)也一定返回true。
3. 傳遞性:對於任意的引用值x、y和z,如果x.equals(y)返回true,
  並且y.equals(z)也返回true,那麼x.equals(z)也一定返回true。
4. 一致性:對於任意的引用值x 和 y,如果用於equals比較的對象信息沒有被修
  改,多次調用x.equals(y)要麼一致地返回true,要麼一致地返回false。
5. 非空性:對於任意的非空引用值x,x.equals(null)一定返回false。
接下來我們通過實例來理解上面的約定。我們首先以一個簡單的非可變的二維點類作爲開始:
public class Point{
  private final int x;
  private final int y;
  public Point(int x, int y){
    this.x = x;
    this.y = y;
  }

  public boolean equals(Object o){
    if(!(o instanceof Point))
      return false;
    Point p = (Point)o;
      return p.x == x && p.y == y;
  }

}


假設你想要擴展這個類,爲一個點增加顏色信息:
public class ColorPoint extends Point{
  private Color color;
  public ColorPoint(int x, int y, Color color){
    super(x, y);
    this.color = color;
  }

  //override equasl()

  public boolean equals(Object o){
    if(!(o instanceof ColorPoint))
     return false;
    ColorPoint cp = (ColorPoint)o;
    return super.equals(o) && cp.color==color;
  }
}


  我們重寫了equals方法,只有當實參是另一個有色點,並且具有同樣的位置和顏色的時候,它才返回true。可這個方法的問題在於,你在比較一個普通點和一個有色點,以及反過來的情形的時候,可能會得到不同的結果:
public static void main(String[] args){
  Point p = new Point(1, 2);
  ColorPoint cp = new ColorPoint(1, 2, Color.RED);
  System.out.println(p.equals(cp));
  System.out.println(cp.eqauls(p));
}

運行結果:
true  
false
這樣的結果顯然違反了對稱性,你可以做這樣的嘗試來修正這個問題:讓ColorPoint.equals在進行“混合比較”的時候忽略顏色信息:
public boolean equals(Object o){
  if(!(o instanceof Point))
    return false;
  //如果o是一個普通點,就忽略顏色信息
  if(!(o instanceof ColorPoint))
    return o.equals(this);
  //如果o是一個有色點,就做完整的比較
  ColorPoint cp = (ColorPoint)o;
  return super.equals(o) && cp.color==color;
}

這種方法的結果會怎樣呢?讓我們先來測試一下:
public static void main(String[] args){
  ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
  Point p2 = new Point(1, 2);
  ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
  System.out.println(p1.equals(p2));
  System.out.println(p2.equals(p1));
  System.out.println(p2.equals(p3));
  System.out.println(p1.eqauls(p3));
}

運行結果:
true
true
true
false

  這種方法確實提供了對稱性,但是卻犧牲了傳遞性(按照約定,p1.equals(p2)和p2.eqauals(p3)都返回true,p1.equals(p3)也應返回true)。要怎麼解決呢?事實上,這是面嚮對象語言中關於等價關係的一個基本問題。要想在擴展一個可實例化的類的同時,既要增加新的特徵,同時還要保留equals約定,沒有一個簡單的辦法可以做到這一點。新的解決辦法就是不再讓ColorPoint擴展Point,而是在ColorPoint中加入一個私有的Point域,以及一個公有的視圖(view)方法:
public class ColorPoint{
  private Point point;
  private Color color;
  public ColorPoint(int x, int y, Color color){
    point = new Point(x, y);
    this.color = color;
  }

  //返回一個與該有色點在同一位置上的普通Point對象
  public Point asPoint(){
    return point;
  }

  public boolean equals(Object o){
    if(o == this)
     return true;
    if(!(o instanceof ColorPoint))
     return false;
    ColorPoint cp = (ColorPoint)o;
    return cp.point.equals(point)&&
             cp.color.equals(color);

  }
}

  還有另外一個解決的辦法就是把Point設計成一個抽象的類(abstract class),這樣你就可以在該抽象類的子類中增加新的特徵,而不會違反equals約定。因爲抽象類無法創建類的實例,那麼前面所述的種種問題都不會發生。

重寫equals方法的要點:
1. 使用==操作符檢查“實參是否爲指向對象的一個引用”。
2. 使用instanceof操作符檢查“實參是否爲正確的類型”。
3. 把實參轉換到正確的類型。
4. 對於該類中每一個“關鍵”域,檢查實參中的域與當前對象中對應的域值是否匹
  配。對於既不是float也不是double類型的基本類型的域,可以使用==操作符
  進行比較;對於對象引用類型的域,可以遞歸地調用所引用的對象的equals方法;
  對於float類型的域,先使用Float.floatToIntBits轉換成int類型的值,
  然後使用==操作符比較int類型的值;對於double類型的域,先使用
  Double.doubleToLongBits轉換成long類型的值,然後使用==操作符比較
  long類型的值。
5. 當你編寫完成了equals方法之後,應該問自己三個問題:它是否是對稱的、傳
  遞的、一致的?(其他兩個特性通常會自行滿足)如果答案是否定的,那麼請找到
  這些特性未能滿足的原因,再修改equals方法的代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章