當前位置:
首頁 > 知識 > 對於所有對象都通用方法的解讀

對於所有對象都通用方法的解讀

這篇博文主要介紹覆蓋Object中的方法要注意的事項以及Comparable.compareTo方法。

一、謹慎覆蓋equals方法

其實平時很少要用到覆蓋equals方法的情況,沒有什麼特殊情況最好是使用原有提供的equlas方法。因為覆蓋equals方法時要遵循一些通用的約定之外,在與hash相關的集合類使用時,就必須要覆蓋hashCode方法了(第二點會強調)。

我們先說說覆蓋equlas方法要遵循哪些通用約定:

1、自反性:對於任何非null的引用值x, x.equals(x)必須返回true;

2、對稱性:對於任何非null的引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true;

3、傳遞性:對於任何非null的引用值x、y和z,當且僅當x.equals(y)返回true,並且y.equals(z)也返回true,那麼x.equals(z)也必須返回true;

4、一致性:對於任何一個非null的引用值x和y,只要equals的比較操作在對象中所用的信息沒有被修改,多次調用x.equals(y)的結果依然一致。;

5、非空性:對於任何非null的引用值,x.equals(null)必須返回false。 且x必須不能為空,否則會拋出空指針異常。

其實上面這些約定看起來都看簡單但也不能大意,很有可能你只滿足了其中一點或者幾點沒有滿足全部。如果沒有滿足全部的話,那麼你覆蓋equals方法是不合理的,總會有那麼中情況會超乎你的意料之外。

下面我們舉例說明,其中自反性是很難違反的(我也想像不出怎麼舉反例測試,如有高手明白,請舉例評論下),下面我舉例驗證對稱性:

1 package test.effective;
2 /**
3 * @Description: 普通坐標類
4 * @author yuanfy
5 * @date 2017年7月13日 上午10:30:06
6 */
7 class Point {
8 private int x;
9 private int y;
10
11 public Point(int x, int y) {
12 this.x = x;
13 this.y = y;
14 }
15
16 @Override
17 public boolean equals(Object obj) {
18 if (!(obj instanceof Point)) {
19 return false;
20 }
21 Point p = (Point) obj;
22 return this.x == p.x && this.y == p.y;
23 }
24 }
25 /**
26 * @Description: 帶有顏色的坐標
27 * @author yuanfy
28 * @date 2017年7月13日 上午10:35:19
29 */
30 class ColorPoint extends Point {
31
32 private String color;
33
34 public ColorPoint(int x, int y, String color) {
35 super(x, y);
36 this.color = color;
37 }
38 /**
39 * 與point不滿足對稱性示範案例
40 */
41 @Override
42 public boolean equals(Object obj) {
43 if (!(obj instanceof ColorPoint)) {
44 return false;//與point實例比較直接返回false
45 }
46 ColorPoint p = (ColorPoint) obj;
47 return super.equals(p) && this.color.equals(p.color);
48 }
49 }
50
51 public class EqualsTest {
52 public static void main(String[] args) {
53 Point p = new Point(1,2);
54 ColorPoint cp = new ColorPoint(1, 2, "red");
55
56 System.out.println(p.equals(cp));//輸出結果:true
57 System.out.println(cp.equals(p));//輸出結果:false
58 }
59 }

從上面的例子可以看出是違反了對稱性規定的。問題原因在於:在比較普通點和有色點時,忽略了顏色的比較,而有色點跟普通點比較時,普通點不屬於ColorPoint的實例,就直接返回了false。所以ColorPoint類中覆蓋equlas方法是有問題的,修改equals方法後的代碼如下:

1 @Override
2 public boolean equals(Object obj) {
3 //不屬於Point實例對象
4 if (!(obj instanceof Point)) {
5 return false;
6 }
7 //不是ColorPoint實例對象,可能是Point實例對象或者其他類型對象
8 if (!(obj instanceof ColorPoint)) {
9 return super.equals(obj);
10 }
11 //ColorPoint實例對象
12 ColorPoint p = (ColorPoint) obj;
13 return super.equals(p) && this.color.equals(p.color);
14 }

測試代碼如下:

1 ColorPoint cp1 = new ColorPoint(1, 2, "red");
2 Point p = new Point(1,2);
3 ColorPoint cp2 = new ColorPoint(1, 2, "blue");
4
5 System.out.println(cp1.equals(p));//輸出結果:true
6 System.out.println(p.equals(cp1));//輸出結果:true
7
8 System.out.println(p.equals(cp2));//輸出結果:true
9 System.out.println(cp2.equals(p));//輸出結果:true
10
11 System.out.println(cp1.equals(p));//輸出結果:true
12 System.out.println(p.equals(cp2));//輸出結果:true
13 System.out.println(cp1.equals(cp2));//輸出結果:false

從修改後的例子中可以看出,這種方法確實滿足了對稱性,但是卻不滿足傳遞性。其實我們無法在擴展可實例化的類的同時,既增加新的值組件,同時又保留equals約定,除非願意放棄面向對象的抽象帶來的優勢。當然我們可以不擴展Point的,所謂「複合優先於繼承」。在ColorPoint假如一個私有的Point域,代碼如下:

1 class ColorPoint{
2
3 private String color;
4
5 private Point point;
6
7 public ColorPoint(int x, int y, String color) {
8 point = new Point(x, y);
9 this.color = color;
10 }
11
12 public Point asPoint{
13 return point;
14 }
15
16 @Override
17 public boolean equals(Object obj) {
18 if (!(obj instanceof ColorPoint)) {//滿足非空性驗證
19 return false;
20 }
21 //ColorPoint實例對象
22 ColorPoint p = (ColorPoint) obj;
23 return this.point.equals(p.point) && this.color.equals(p.color);
24 }
25 }
26
27 public class EqualsTest1 {
28 public static void main(String[] args) {
29 ColorPoint cp1 = new ColorPoint(1, 2, "red");
30 Point p = new Point(1,2);
31 ColorPoint cp2 = new ColorPoint(1, 2, "red");
32 ColorPoint cp3 = new ColorPoint(1, 2, "red");
33
34 System.out.println(cp1.equals(p));//輸出結果:false
35 System.out.println(p.equals(cp1));//輸出結果:false
36
37 System.out.println(cp1.equals(cp2));//輸出結果:true
38 System.out.println(cp2.equals(cp1));//輸出結果:true
39
40 System.out.println(cp1.equals(cp2));//輸出結果:true
41 System.out.println(cp2.equals(cp3));//輸出結果:true
42 System.out.println(cp1.equals(cp3));//輸出結果:true
43 }
44 }

上面的例子就滿足對稱性、傳遞性同時滿足非空性。

接下來驗證一致性:如果兩個對象相等,它們就必須始終保持相等,除非他們中有一個對象或者兩個都被修改了。換句話說,可變的對象在不同的時候可以與不同的對象相等, 而不可變對象則不會這樣。比如說時間對象一個指定了時間,另外一個對象沒有指定時間。當在某一個刻時候,他們既滿足對稱性和傳遞性,但是它不滿足一致性。因為沒有指定時間的對象的時間是一直在改變的。

所以當你編寫完成了equlas之後,應該測試驗證是否滿足這個幾個特性。

二、覆蓋equals時總要覆蓋hashCode

書中提出:在每個覆蓋了equlas方法的類,也必須覆蓋hashCode方法。如果不這樣做的話, 就會違反Object.hashCode的通用約定,從而導致該類無法結合所有基於散列的集合一起正常運作,這樣的集合包括HashMap、HashSet和HashTable。

其中約定簡潔如下:

1、相等的對象必須具有相等的散列碼(hash code)

2、不相等的對象未必是不一樣的散列碼。也就是說相同散列碼的兩個對象,兩個對象未必相等。但是相等的兩個對象,一定就有相等的散列碼。

下面根據例子來說明:

1 package test.effective;
2
3 import java.util.HashMap;
4 import java.util.Map;
5
6 public class PhoneNumber {
7 private int areaCode;
8
9 private int prefix;
10
11 private int lineNumer;
12
13 public PhoneNumber(int areaCode, int prefix, int lineNumer) {
14 this.areaCode = areaCode;
15 this.prefix = prefix;
16 this.lineNumer = lineNumer;
17 }
18
19 @Override
20 public boolean equals(Object obj) {
21 if (obj == this) {
22 return true;
23 }
24 if (!(obj instanceof PhoneNumber)) {
25 return false;
26 }
27 PhoneNumber pn = (PhoneNumber) obj;
28 return this.areaCode == pn.areaCode
29 && this.prefix == pn.prefix
30 && this.lineNumer == this.lineNumer;
31 }
32
33 public static void main(String[] args) {
34
35 Map map = new HashMap;
36 PhoneNumber pn1 = new PhoneNumber(408, 867, 5309);
37 PhoneNumber pn2 = new PhoneNumber(408, 867, 5309);
38
39 System.out.println(pn1.equals(pn2));//輸出結果:true
40
41 map.put(pn1, "Jany");
42
43 System.out.println(map.get(pn1));
44 System.out.println(map.get(pn2));
45 }
46 }

我們將覆蓋了equals方法的類結合基於散列碼的集合使用。從上面例子39行知道pn1實例和pn2實例是相等,行43代碼輸出的結果可想而知是Jany,但是行44輸出什麼呢,估計沒有仔細考慮的話,可能第一感覺也會輸出null。我們看下HashMap中get方法源碼就知道了。

public V get(Object key) {
if (key == null)
return getForNullKey;
Entry entry = getEntry(key);

return null == entry ? null : entry.getValue;
}
final Entry getEntry(Object key) {
if (size == 0) {
return null;
}

int hash = (key == null) ? 0 : hash(key);//獲取對象的hash值
for (Entry e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
//hash必須相等才有可能進行返回
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}

從源碼中得知get方法是要根據hash值獲取的。然後我們在看看pn1和pn2對象的hash碼。

System.out.println(pn1.hashCode);//2007692877
System.out.println(pn2.hashCode);//2031122075

所以顯而易見見,上述map.get(pn2) 的返回值是為null的。

修正這個問題很簡單,在PhoneNumber類中覆蓋hashCode方法即可,先提供簡單的案例:

@Override
public int hashCode {
return 1;
}

這樣的能滿足上面那個例子map.get(pn2) 返回「Jany」,但是這樣 所有實例都是返回一樣的hashCode是不可取的。參考書中最正確的方式:

@Override
public int hashCode {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumer;
return 1;
}

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 達人科技 的精彩文章:

Spark源碼閱讀之存儲體系--存儲體系概述與shuffle服務
使用C創建WCF服務控制台應用程序

TAG:達人科技 |

您可能感興趣

養蘭花,沒有統一的方法,就用4個通用法
在通用AI出現之前,是否還有其他方法讓機器人走進家庭?
有沒有一種通用的學習方式?
學習素描的幾個通用方法,今天你應該掌握了它!
蒯通用什麼方法使自己無罪釋放?
終於,「最近鄰搜索」有通用方法了
不同坦克之間能通用底盤嗎?除了方便通用化,其餘一無是處
咳嗽不止?這有個好用的通用方!
聯合國拒用日語作為官方通用語言?專家做出解釋:還沒有資格!
想要忘記深愛的人,最好用這三個方法(男女通用)
全國方言眾多,在古代用什麼通用語言交流,也是普通話嗎
公認最有效的防衰方法,你能做到幾個?男女通用
通用的4個跑步方法,大家雖然都知道,但是在訓練中卻做不到!
神准愛情占卜:你如今愛的人,對你又是什麼感覺與想法?男女通用
她的麵包機配方,不僅組織細膩口感好,還通用!
正一道常見法器的通用說明書
幾個簡單的自我療法能讓你心情變好,普通人和抑鬱患者通用!
通用在韓投資方案確定 最終還取決於政府及工會
被「分手」很傷心?教你「男女通用」神回復,讓對方哭的稀里嘩啦
解放軍裝輸各種通用…為何「通用底盤」反而孤家寡人?