国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

Effective Java

2019-11-14 22:20:46
字體:
供稿:網(wǎng)友
Effective java - 謹(jǐn)慎覆蓋equals

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

什么時候不需要覆蓋equals?

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

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

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

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

其實這些規(guī)定隨便拿出一個都是很好理解的。難點(diǎn)在于,當(dāng)我遵守一個規(guī)定時有可能違反另一個規(guī)定。

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

關(guān)于對稱性,下面提供一個反面例子:

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。不僅是在顯示調(diào)用時,如果將這種類型作為泛型放到集合之類的地方,會發(fā)生難以預(yù)料的行為。

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

關(guān)于傳遞性,即,當(dāng)x.equals(y)為true 且 y.equals(z)為true 則 x.equals(z)為true。這個規(guī)定在對類進(jìn)行擴(kuò)展時尤其明顯。

比如,我用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;    }}

現(xiàn)在我想給Point加點(diǎn)顏色:

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方法,但他連對稱性的沒能滿足。于是我們加以修改,令其滿足對稱性:

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

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

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

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

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

關(guān)于一致性,即如果兩者相等則始終相等,除非有一方被修改。這一點(diǎn)與其說equals方法,到不如思考寫一個類的時候,這個類應(yīng)該設(shè)計成可變還是不可變。如果是不可變的,則需要保證一致性。

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

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

任何覆蓋了equals方法的類都需要覆蓋hashCode方法。忽視這一條將導(dǎo)致類無法與基于散列的數(shù)據(jù)結(jié)構(gòu)一起正常工作,比如和HashMap、HashSet和Hashtable。

下面是hashCode相關(guān)規(guī)范:

  • 在程序執(zhí)行期間,只要對象的equals方法的比較操作所用到的信息沒有被修改,那么對這個對象調(diào)用多少次hashCode,起結(jié)果必須始終如一地返回同一個證書。如果是同一個程序執(zhí)行多次,每次調(diào)用的結(jié)果可以不一致。

  • 如果兩個對象根據(jù)equals方法比較是相等的,那么兩個對象的hashCode結(jié)果必須相同。

  • 如果兩個對象根據(jù)equals方法比較是不相等的,那么這兩個對象的hashCode不一定返回不同的結(jié)果。但是,如果不同的對象返回不同的hashCode,則能提高散列表的性能。

下面的代碼是一個反面例子:

import java.util.HashMap;import java.util.Map;public final class PhoneNumber {    private final short areaCode;    private final short prefix;    private final short lineNumber;    public PhoneNumber(int areaCode, int prefix, int lineNumber) {        rangeCheck(areaCode, 999, "area code");        rangeCheck(prefix, 999, "prefix");        rangeCheck(lineNumber, 9999, "line number");        this.areaCode = (short) areaCode;        this.prefix = (short) prefix;        this.lineNumber = (short) lineNumber;    }    private static void rangeCheck(int arg, int max, String name) {        if (arg < 0 || arg > max)            throw new IllegalArgumentException(name + ": " + arg);    }    @Override    public boolean equals(Object o) {        if (o == this)            return true;        if (!(o instanceof PhoneNumber))            return false;        PhoneNumber pn = (PhoneNumber) o;        return pn.lineNumber == lineNumber && pn.prefix == prefix                && pn.areaCode == areaCode;    }    // Broken - no hashCode method!    // A decent hashCode method - Page 48    // @Override public int hashCode() {    // int result = 17;    // result = 31 * result + areaCode;    // result = 31 * result + prefix;    // result = 31 * result + lineNumber;    // return result;    // }    public static void main(String[] args) {        Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();        m.put(new PhoneNumber(707, 867, 5309), "Jenny");        System.out.println(m.get(new PhoneNumber(707, 867, 5309)));    }}

通過equals方法比較,兩個實例在邏輯上是相等的。但由于沒有覆蓋hashCode方法,兩個實例返回的hashCode是不同的。在散列表中,如果散列碼不匹配,就不必檢查兩個實例是否相等。如果隨便提供這樣的一個hashCode方法:

public int hashCode(){    return 42;}

這樣會讓散列表失去優(yōu)勢,退化為鏈表。

最好的hashCode應(yīng)該是<不同的對象產(chǎn)生不同的散列碼>。即,散列函數(shù)把集合中不同的實例均勻地分布到所有可能的散列值上。

下面是一種簡單的思路(也就是上面例子中注釋的部分):

  • 把一個非零常數(shù)值放在result變量中。
  • 針對每一個關(guān)鍵的field(假設(shè)變量名為f)計算int類型的散列碼,不同類型有不同的計算方式。

    • boolean:f?1:0
    • byte,short,char:(int)f
    • long:(int)(f^(f>>>32))
    • float:Float.floatToIntBits(f)
    • double:Double.doubleToIntBits(f)
    • 引用:遞歸調(diào)用hashCode
    • 數(shù)組:每個元素作為一個field遵循上述規(guī)則
  • 對計算出的散列碼值c進(jìn)行:result = result*31+c;
  • 重復(fù)測試。

注意,這里僅限關(guān)鍵field。對于那些用其他field值計算出來的field,我們可以將其排除在外。

如果一個類是不可變的,而且計算散列值的開銷比較大,我們可以試著將散列值緩存?;蛘呶覀円部梢栽囋囇舆t初始化,在hashCode第一次被調(diào)用時進(jìn)行初始化:

private volatile int hashCode; // (See Item 71)@Override public int hashCode() {    int result = hashCode;    if (result == 0) {        result = 17;        result = 31 * result + areaCode;        result = 31 * result + prefix;        result = 31 * result + lineNumber;        hashCode = result;    }    return result;}

另外,Josh Bloch在最后加了一段話:

Many classes in the Java platform libraries, such as String, Integer, and Date, include in their specifications the exact value returned by their hashCode method as a function of the instance value. This is generally not a good idea, as it severely limits your ability to improve the hash function in future releases.

<可以把它們的hashCode方法返回的確切值規(guī)定為該實例的一個函數(shù)。> 看了翻譯后一頭霧水...

后來在爆棧中看到這么一個回復(fù),記下來作為參考:

The API docs specify that String.hashCode() is computed by a specific formula. Client code is free to independently compute the hash code using that exact formula and assume it will be the same as that returned by String.hashCode(). This might seem perverse for pure Java code, but does make some sense with JNI. There are probably other cases where it would make sense to take advantage of the extra knowledge that the API specifies.


發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 肥西县| 沂水县| 大荔县| 黄陵县| 黔南| 乐亭县| 泰和县| 集安市| 拉萨市| 宁武县| 洪泽县| 咸宁市| 高陵县| 荆门市| 米泉市| 徐汇区| 平和县| 潜山县| 怀远县| 多伦县| 花莲县| 紫金县| 临漳县| 宜丰县| 裕民县| 招远市| 六盘水市| 泰来县| 汤阴县| 宽城| 綦江县| 治多县| 通渭县| 同仁县| 亚东县| 铜鼓县| 宜川县| 阿拉尔市| 买车| 宜都市| 德钦县|