歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Effective Java - 謹慎覆蓋equals

平時很難遇到需要覆蓋equals的情況。

什麼時候不需要覆蓋equals?

  • 類的每個實例本質上是唯一的,我們不需要用特殊的邏輯值來表述,Object提供的equals方法正好是正確的。
  • 超類已經覆蓋了equals,且從超類繼承過來的行為對於子類也是合適的。
  • 當確定該類的equals方法不會被調用時,比如類是私有的。

如果要問什麼時候需要覆蓋equals?
答案正好和之前的問題相反。
即,類需要一個自己特有的邏輯相等概念,而且超類提供的equals不滿足自己的行為。
(PS:對於枚舉而言,邏輯相等和對象相等都是一回事。)

既然只好覆蓋equals,我們就需要遵守一些規定:

  • 自反性 (reflexive):對於任何一個非null的引用值x,x.equals(x)為true。
  • 對稱性 (symmetric):對於任何一個非null的引用值x和y,x.equals(y)為true時y.equals(x)為true。
  • 傳遞性 (transitive):對於任何一個非null的引用值x、y和z,當x.equals(y)為true 且 y.equals(z)為true 則 x.equals(z)為true。
  • 一致性 (consistent):對於任何一個非null的引用值x和y,只要equals的比較操作在對象中所用的信息沒有被修改,多次調用x.equals(y)的結果依然一致。
    (PS:對於任何非null的引用值x,x.equals(null)必須返回false。)

其實這些規定隨便拿出一個都是很好理解的。
難點在於,當我遵守一個規定時有可能違反另一個規定

自反性就不用說了,很難想想會有人違反這一點。

關於對稱性,下面提供一個反面例子:

class CaseInsensitiveString {

    private final String s;

    public CaseInsensitiveString(String s) {
        if (s == null)
            this.s = StringUtils.EMPTY;
        else
            this.s = s;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof CaseInsensitiveString)
            return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s);
        if (obj instanceof String)
            return s.equalsIgnoreCase((String) obj);
        return false;
    }

}

這個例子顯然違反對稱性,即x.equals(y)為true 但 y.equals(x)為false。
不僅是在顯示調用時,如果將這種類型作為泛型放到集合之類的地方,會發生難以預料的行為。

而對於上面這個例子,在equals方法中我就不牽扯其他類型,去掉String實例的判斷就可以了。

關於傳遞性,即,當x.equals(y)為true 且 y.equals(z)為true 則 x.equals(z)為true。
這個規定在對類進行擴展時尤其明顯。

比如,我用x,y描述某個Point:

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

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

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

}


現在我想給Point加點顏色:

class ColorPoint extends Point {
    private final Color color;

    public ColorPoint(int x, int y, Color color) {
        super(x, y);
        this.color = color;
    }

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

}

似乎很自然的提供了ColorPoint的equals方法,但他連對稱性的沒能滿足。

於是我們加以修改,令其滿足對稱性:

@Override
public boolean equals(Object obj) {
    if (!(obj instanceof Point))
        return false;
    if (!(obj instanceof ColorPoint))
        return obj.equals(this);
    return super.equals(obj) && ((ColorPoint) obj).color == color;
}


好了,接下來我們就該考慮傳遞性了。
比如我們現在有三個實例,1個Point和2個ColorPoint....
然後很顯然,不滿足<當x.equals(y)為true 且 y.equals(z)為true 則 x.equals(z)為true>。
事實上,我們無法在擴展可實例化類的同時,既增加新的值組件,又保留equals約定。

於是我索性不用instanceof,改用getClass()。
這個確實可以解決問題,但很難令人接受。
如果我有一個子類沒有覆蓋equals,此時equals的結果永遠是false。

既然如此,我就放棄繼承,改用復合(composition)。
以上面的ColorPoint作為例子,將Point變成ColorPoint的field,而不是去擴展。 代碼如下:

public class ColorPoint {
    private final Point point;
    private final Color color;

    public ColorPoint(int x, int y, Color color) {
        if (color == null)
            throw new NullPointerException();
        point = new Point(x, y);
        this.color = color;
    }

    /**
     * Returns the point-view of this color point.
     */
    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);
    }

    @Override
    public int hashCode() {
        return point.hashCode() * 33 + color.hashCode();
    }
}

關於一致性,即如果兩者相等則始終相等,除非有一方被修改。
這一點與其說equals方法,到不如思考寫一個類的時候,這個類應該設計成可變還是不可變。
如果是不可變的,則需要保證一致性。

考慮到這些規定,以下是重寫equals時的一些建議:

  • 第一步使用"=="操作驗證是否為同一個引用,以免不必要的比較操作。
  • 使用instanceof檢查參數的類型。
  • 檢查所有關鍵的field,對float和double以外的基本類型field直接使用"=="比較。
  • 回過頭來重新檢查一遍:是否滿足自反性、對稱性、傳遞性和一致性。

Copyright © Linux教程網 All Rights Reserved