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方法,否則違反了相等的對象必須有相等的散列碼。
這個下一篇再記述。