1. 程式人生 > >effectiveJava學習筆記:覆蓋equals時遵守的約定

effectiveJava學習筆記:覆蓋equals時遵守的約定

hashCode 方法用於雜湊集合的查詢,equals 方法用於判斷兩個物件是否相等。

我們為什麼需要重寫hashCode()方法和equals()方法?

有時在我們的業務系統中判斷物件時有時候需要的不是一種嚴格意義上的相等,而是一種業務上的物件相等。在這種情況下,原生的equals方法就不能滿足我們的需求了.我們所知道的JavaBean的超類(父類)是Object類,JavaBean中的equals方法是繼承自Object中的方法.Object類中定義的equals()方法是用來比較兩個引用所指向的物件的記憶體地址是否一致.並不是比較兩個物件的屬性值是否一致,所以這時我們需要重寫equals()方法.

覆蓋equals時遵守下面的約定

  • 自反性(reflexive):對於任何非null的引用值x,x.equals(x)必須返回true
  • 對稱性(symmetric):對於任何非null的引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)必須返回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)就會一致地返回true,或者一致地返回false
  • 非空性(Non—nullity):對於任何非null的引用值x,x.equals(null)必須返回false

1、自反性。

如果不是特意一般是不會在第一條犯錯的。不需要特意說明。

2、對稱性。

NotSring類中的equals方法是忽略大小寫的,只要值相等即返回true。但是String類中的equals並不會忽略大小寫,即使String類本身已經重寫了equals,只要值相等就返回true,但這裡恰恰是大小寫不同值相同的一種方式,所以就使得s.equals(ns)返回false了。

package com.ligz.two.equals;

/**
 * @author ligz
 */
public class NotString {
	private final String s;
	
	public NotString(String s) {
		if(s == null) {
			throw new NullPointerException();
		}
		this.s = s;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof NotString) {
			return s.equalsIgnoreCase(((NotString) obj).s);
		}
		
		if(obj instanceof String) {
			return s.equalsIgnoreCase((String)obj);
		}
		return false;
	}
}

3、傳遞性。

下面的Point和其子類ColorPoint,在子類新加一個顏色特性,就會很容易違反這條約定。

首先我們看違反對稱性的程式碼

Point.java

package com.ligz.two.equals;

/**
 * @author ligz
 */
public 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;
        else {
            Point p = (Point)obj;
            return p.x==x&&p.y==y;
        }
    }
}

ColorPoint.java

package com.ligz.two.equals;

/**
 * @author ligz
 */
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 obj) {
        if(!(obj instanceof ColorPoint))
            return false;
        else
            return super.equals(obj)&&((ColorPoint)obj).color==color;
    }
}

測試對稱性

package com.ligz.two.equals;

/**
 * @author ligz
 */
public class ColorTest {
	public static void main(String[] args) {
		Point p = new Point(1, 2);
        ColorPoint cp = new ColorPoint(1, 2, "red");
        ColorPoint cp2 = new ColorPoint(1, 2, "blue");
        System.out.println("p覆蓋的equals");
        System.out.println(p.equals(cp));
        System.out.println("cp覆蓋的equals");
        System.out.println(cp.equals(p));
	}
}

不滿足對稱性,我們在ColorPoint.java中加上忽略顏色資訊的程式碼

package com.ligz.two.equals;

/**
 * @author ligz
 */
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 obj) {
    	//這是改變的地方
        if(!(obj instanceof Point))
            return false;
        
        if(!(obj instanceof ColorPoint))
        	return obj.equals(this);
        
        return super.equals(obj)&&((ColorPoint)obj).color==color;
    }
}

發現測試都是true

但是這樣卻犧牲了傳遞性

我們無法在擴充套件可例項化類的同時,既增加新的值元件,同時又保留equals約定,除非願意放棄面向物件的優勢即組合優先於繼承的方法實現。

4、一致性

如果兩個物件相等,它們就必須保持相等,除非它們中有一個物件(或者兩個都)被修改了

5、非空性

@override
public boolean equals(Object obj){
    if(obj == null)
        return false;
    ....
}

實現高質量equals的訣竅

1、使用==操作符檢查“引數是否為這個物件的引用”。如果是則返回true。

2、使用instanceof操作符檢查“引數是否為正確的型別”,如果不是則返回false。所謂的正確的型別是指equals方法所在的那個類,或者是該類的父類或介面

3、把引數轉化成正確的型別:因為上一步已經做過instanceof測試,所以確保轉化會成功

4、對於該類的每個“關鍵”域,檢查引數中的域是否與該物件中對應的域相匹配(其實就是比較兩個物件的值是否相等了)

5、當你編寫完equals方法之後,應該問自己三個問題:它是否是對稱的、傳遞的、一致的。
 

下面是根據上面的約定和訣竅覆蓋equals的方法。

package com.ligz.two.equals;

/**
 * @author ligz
 */
public final class PhoneNumber {
	private final short areaCode;
    private final short prefix;
    private final short lineNumber;

    public PhoneNumber(short areaCode, short prefix, short 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 obj) {
        //1、引數是否為這個物件的引用
        if(obj == this)
            return true;
        //2、使用instanceof檢查
        if(!(obj instanceof PhoneNumber))
            return false;
        //3、把引數轉化成正確的型別
        PhoneNumber pn = (PhoneNumber)obj;
        //4、比較兩個物件的值是否相等
        return pn.lineNumber == lineNumber
            && pn.prefix == prefix
            && pn.areaCode == areaCode;
    }
}