覆蓋equals時請遵守通用約定

不覆蓋equals的幾種情況

  1. 類的每個實例本質(zhì)上都是唯一的(唯一了 用父類的equals判斷就可以了)
  2. 不關(guān)心類是否提供了“邏輯相等”的測試功能(沒有需要判斷邏輯相等的需求)
  3. 超類已經(jīng)覆蓋了equals,從超類繼承過來的行為對于子類也是合適的。(父類的equals對于子類夠用了)
  4. 類是私有的或者是包級私有的,可以確定它的equals方法永遠不會被調(diào)用。(這個類別人用不了,也確定了不會調(diào)用equals)

為什么我們要覆蓋equals

當一個類具有“邏輯相等”的概念并且超類沒有覆蓋equals方法時,需要覆蓋equals.通常這種是“值類”,即僅僅表示一個值得類。例如Integer,Date等。常常我們關(guān)心的是邏輯上是否是同一個對象 ,而不是是否指向同一個對象。邏輯相等還有一個好處是可以作為map的key或者集合set的元素。

覆蓋equals時需要遵循的約定(JavaSE6的規(guī)范)

  1. 自反性:對于任何非null的引用值x,x.equals(x)必須返回true
  2. 對稱性: 對于任何非null的引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true
  3. 傳遞性:對于任何非null的引用值,x,y,z,如果x.equals(y)為true,并且y.equals(z)也返回true,那么x.equals(z)也必須返回true
  4. 一致性:對于任何非null的引用值x和y,只要equals的比較操作在對象中所用的信息沒有被修改,多次調(diào)用x.equals(y)就會一致地返回true,或者false
  5. 非空性:對于任何非null的引用值,x,x.equals(null)必須返回false

自反性

基本上很難違反這一條。

對稱性

public final class CaseInsensitiveString{
    private final String s;
    public CaseInsensitiveString(String s){
        if(s == null){
            throw new NullPointerException();
        }
        this.s = s;
    }
  @Override 
  public boolean equals(Object o){
        if(o  instanceof  CaseInsensitiveString){
            return s.equalsIgnoreCase(((CaseInsensitiveString)o).s);
        }
        if(o  instanceof  String){
            return s.equalsIgnoreCase((String)o);
        }
        return false;
    }
}
public class Test {
    public static void main(String[] args) {
        CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
        String s = "polish";
        System.out.println(cis.equals(s));
        System.out.println(s.equals(cis));

    }
}

這個類企圖兼容與String能夠做比較實則違反了對稱性

解決辦法重寫CaseInsensitiveString的equals方法
@Override public boolean equals(Object o){
//專一,只與CaseInsensitiveString類自己的實例比較
return o instanceof CaseInsensitiveString 
       && ((CaseInsensitiveString)o).s.equalsIgnoreCase(s);
}

傳遞性

public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    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 final String color;
    public ColorPoint(int x, int y, String color){
        super(x, y);
        this.color = color;
    }
    @Override public boolean equals(Object o){
        if(!(o instanceof  ColorPoint)){
            return false;
        }
        return super.equals(o) && ((ColorPoint)o).color == color;
    }
}
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, "red");

此時違反了對稱性

然后我們修改了equals方法

@Override public boolean equals(Object o){
        //非Point或ColorPoint
        if(!(o instanceof  Point)){
            return false;
        }
        if(!(o instanceof  ColorPoint)){//o為不帶顏色的Point,使用Point的equals方法比較
            return o.equals(this);
        }
        //o為ColorPoint
        return super.equals(o) && ((ColorPoint)o).color == color;
    }
public class Test {
    public static void main(String[] args) {
        ColorPoint p1 = new ColorPoint(1, 2, "red");
        Point p2 = new Point(1, 2);
        ColorPoint p3 = new ColorPoint(1, 2, "green");
        System.out.println(p1.equals(p2));
        System.out.println(p2.equals(p3));
        System.out.println(p3.equals(p1));
    }
}

這個是其中一種容易違反傳遞性的可能,即子類增加了新的屬性,重寫equals,從而影響了傳遞性。
這個問題是面向?qū)ο笳Z言中關(guān)于等價關(guān)系的一個基本問題: 無法在擴展可實例化的類的同時,既增加新的值組件,同時又保留equals的約定。
權(quán)宜之計:復合優(yōu)先于繼承

public class ColorPoint{
    private final Point point;
    private final String color;
    public ColorPoint(int x, int y, String color){
      if(Color == null){
        throw new NullPointerException();
          }
          point = new Point(x, y);
          this.color = color;
        }
    public Point asPoint(){
        return point;
    }
   @Override public Boolean equals(Object o){
     if(!(o instanceof  ColorPoint)){
            return false;
     }
         ColorPoint cp = (ColorPoint)o;
         return cp.point.equals(point) && cp.color.equals(color);
    }
}

一致性

要保證相等的對象永遠相等,不等的對象永遠不等
在equals方法中不要依賴不可靠的資源,例如java.net.URL的equals方法依賴與對URL中主機IP地址的比較,但是網(wǎng)絡中主機IP地址有可能是變化的,因此java.net.URL的equals方法難以保證一致性原則。

非空性

一般我們使用一個顯示的空測試來避免拋出空指針異常:

@Override public boolean equals(Object o){
    if(o == null){
    return false;
}
……
}

其實,在equals方法中,最終是要將待比較對象轉(zhuǎn)換為當前類的實例,以調(diào)用它的方法或訪問它的屬性, 這樣必須先經(jīng)過instanceof測試,而如果instanceof的第一個參數(shù)為null,則不管第二個參數(shù)是那種類型都會返回false,這樣可以很好地避免空指針異常并且不需要單獨的null檢測 。

@Override public boolean equals(Object o){
    if(!(o instanceof  MyType)){
    return false;
}
MyType mt = (MyType)o;
……
}

實現(xiàn)高質(zhì)量equals方法

1、使用==操作符檢查參數(shù)是否為這個對象的引用。(一種優(yōu)化手段 ,如果引用同樣的對象 就完全不用進行后續(xù)可能出現(xiàn)的復雜的判斷)

2、使用instanceof操作符檢查參數(shù)是否為正確的類型。

3、把參數(shù)轉(zhuǎn)換成正確的類型。

4、對于要比較類中的每個關(guān)鍵域,檢查參數(shù)中的域是否與該對象中對應的域相匹配。

5、編寫完equals方法后需要測試是否滿足對稱性、傳遞性和一致性。

最佳編程實踐

1、覆蓋equals時總要覆蓋hashCode

2、不要企圖讓equals方法過于智能

3、不要將equals聲明中的Object對象替換為其他的類型,因為替換后只是重載Object.equals(Object o)而不是覆蓋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容