【JAVA學習】EffectiveJava的學習筆記--第二章 Object類的通用方法

Object類就是爲了擴展用,其中非final方法(equals hashCode,toString,Clone,finalize)耳熟能詳的方法在改寫的時候必須要有很多通用約定,否則其他依賴於這些約定的類就無法與這些類正常工作。


1.equals方法

 1.1什麼時候該改寫equals方法?

       當一個類有自己的邏輯相等(而非對象身份)的概念,超類也沒有改寫equals以實現自己需要的行爲時候,一般都是都是比較兩個指向值對象的引用的時候,希望知道邏輯是否相等,而非指向同一個對象。

1.2 實現改寫的通用約定

  •    自反性:   x.equals(x) 爲true
  •    對稱性:   x.equals(y) 爲true,則   y.equals(x)也爲true

下面這段代碼coment部分的如果存在的話就違反了對稱性,即equals方法中CaseInsensitiveString認識String類因此進行了判斷,而反過來String類卻人不認識CaseInsensitiveString類。這種不自覺的錯誤經常不經意就發生。

public class CaseInsensitiveString {
	
	private String s;
	public CaseInsensitiveString(String s) {
		if(s == null) {
			throw new NullPointerException();			
		}
		this.s = s;
	}
	
	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 static void main(String[] args) {
		String s = "abc";
		CaseInsensitiveString cis = new CaseInsensitiveString("Abc");
		CaseInsensitiveString cis2 = new CaseInsensitiveString("ABC");
		System.out.println("cis.equals(s)= " +cis.equals(s));
		System.out.println("s.equals(cis) = " + s.equals(cis));
		System.out.println("cis2.equals(cis) = " + cis2.equals(cis));
	}
	
}

comment之前的結果是:

cis.equals(s)= true
s.equals(cis) = false
cis2.equals(cis) = true

comment段後的結果是:

cis.equals(s)= false
s.equals(cis) = false
cis2.equals(cis) = true

  •    傳遞性:    x.equals(y) 爲true    y.equals(z)爲true, 則  x.equals(z)也爲true

         這個問題經常發生在繼承擴展一個值的類的時候,如果繼承擴展的類沒有override equals則,調用equals的時候就會出現以下問題。

import java.awt.Color;
import java.awt.Point;

public class ColorPoint  extends Point{
	private static final long serialVersionUID = 1L;
	private Color  color;
	public ColorPoint(int x, int y, Color color) {
		super(x,y);
		this.color = color;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Point p = new Point(1,2);
		ColorPoint cp1 = new ColorPoint(1, 2, Color.red);
		ColorPoint cp2 = new ColorPoint(1, 2, Color.black);		
		System.out.println("p.equals(cp1) =" + p.equals(cp1));
		System.out.println("p.equals(cp2) =" + p.equals(cp2));
		System.out.println("cp1.equals(cp2) =" + cp1.equals(cp2));
	}

}

得到的結果是

p.equals(cp1) =true
p.equals(cp2) =true
cp1.equals(cp2) =true 

但實際上cp1 和cp2是兩個顏色不同的點。override equals方法後,雖然可以保證證對稱性,但沒有保證傳遞性,即p == p1  p==p2  得到的結果是p1 != p2.

	@Override
	public boolean equals(Object obj) {
		// TODO Auto-generated method stub
		if(!(obj instanceof Point)) {
			return false;
		}
		
		// if obj is normal point  do a color-blind comparasion
		if(!(obj instanceof ColorPoint)) {
			return this.equals(obj);
		}
		
		ColorPoint cp = (ColorPoint)obj;
		return super.equals(obj) && (cp.color == this.color);
	}
得到的結果是:

p.equals(cp1) =true
p.equals(cp2) =true
cp1.equals(cp2) =false

爲了解決這個傳遞性的問題,一般要擴展可實例化的類的同時,既要增加新的特徵,又要保留equals的約定,沒有辦法做到。

另外一種解決方案是 採用【複合】的方式。

即ColorPoint不是繼承與Point,而是該類包含了一個Point的成員變量。

  •   非空性:   非空引用值 x  x.equals(null) 則返回false,非空不用多說了,就是上面override裏面的加上null 的判斷。
  • 對於類中每個關鍵域,檢查實參中與當前對象中對應的域是否匹配。 

1.3檢查equals方法的一些小結:

  •           如果該類的類型是接口,則通過接口訪問實參中的關鍵域
  •           如果該類的類型是類,則訪問實參中的關鍵域。
  •           對於不是float 也不是double則可以直接用==比較,對於對象引用域則可以用遞歸的equals方法,
  •           對於float則使用Float.floatToIntBits轉換成int類型的值,再==比較
  •           對於double則使用Double.doubleToLongBits轉換成long型的值,再==比較
  •           對於數組域,則以應用到元素上,有些對象引用域包含null是合法的,所以爲了避免null異常,可以通過判斷該field是不是null然後再進行常規的equals比較。

 1.4 修改equals的時候一定要修改hashcode方法,否則違反了相等的對象必須有相等的散列碼。

     這個下一篇再記述。

 

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