何時(shí)改寫equals方法
當(dāng)一個(gè)類有自己特有的"邏輯相等"概念,而且超類也沒有實(shí)現(xiàn)equals方法實(shí)現(xiàn)期望的行為谬哀,這是我們需要改寫equals方法悦屏。這種比較適合"值類"情形,比如Integer或者Date卖怜。當(dāng)需要自定義類最為HashMap的鍵時(shí)屎开,也需要改寫equals方法,與此同時(shí)還需改寫hashcode()方法。
對(duì)于改寫equals方法所需遵守的通過規(guī)定:
自反性:對(duì)于任何非空引用值 x奄抽,x.equals(x) 都應(yīng)返回 true蔼两。
對(duì)稱性:對(duì)于任何非空引用值 x 和 y,當(dāng)且僅當(dāng) y.equals(x) 返回 true 時(shí)逞度,x.equals(y) 才應(yīng)返回 true额划。
傳遞性:對(duì)于任何非空引用值 x、y 和 z档泽,如果 x.equals(y) 返回 true俊戳,并且 y.equals(z) 返回 true,那么 x.equals(z) 應(yīng)返回 true馆匿。
一致性:對(duì)于任何非空引用值 x 和 y抑胎,多次調(diào)用 x.equals(y) 始終返回 true 或始終返回 false,前提是對(duì)象上 equals 比較中所用的信息沒有被修改渐北。
對(duì)于任何非空引用值 x阿逃,x.equals(null) 都應(yīng)返回 false。
對(duì)于上面幾個(gè)規(guī)則赃蛛,我們在使用的過程中最好遵守恃锉,否則會(huì)出現(xiàn)意想不到的錯(cuò)誤。
這里說明一點(diǎn):對(duì)于改寫equals中的"非空性"呕臂,為了測試實(shí)參與當(dāng)前對(duì)象的相等情況破托,equals方法首先把實(shí)參轉(zhuǎn)為一種適當(dāng)?shù)念愋停谵D(zhuǎn)換之前歧蒋,equals方法必須使用instanceof操作符炼团,檢查實(shí)參是否為正確的類型:
public boolean equals(Object o) {
if (!(o instanceof Mytype)) {
return false;
....
}
}
如果o為null,則類型檢查結(jié)果為false,所以不需做單獨(dú)的null檢查疏尿。
實(shí)現(xiàn)高質(zhì)量的equals方法:
- 使用==操作符檢查"實(shí)參是否為指向?qū)ο蟮囊粋€(gè)引用".
- 使用instanceof操作符檢查"實(shí)參是否為正確的類型"
- 把實(shí)參轉(zhuǎn)換為正確的類型
- 對(duì)于每一個(gè)"關(guān)鍵域",檢查實(shí)參中的域與當(dāng)前對(duì)象中對(duì)應(yīng)的域值是否匹配
比較域值的常用方法(防止原對(duì)象引用域值本身為Null的情況)
(field == null ? o.field == null :field.equals(o.field) );
如果是對(duì)象引用瘟芝,那么下面的方法會(huì)更快一點(diǎn):
(field == o.field || (o.field != null && field.equals(o.field) );
- 改寫equals方法之后,改寫hashcode
- 不要將equals聲明中的Object對(duì)象換成其他類型褥琐。
以下代碼是錯(cuò)誤的例子:
public boolean equals(Mytype o) {
...
}
以下是一個(gè)樣例:
public class Mytype
{
private String field;
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Mytype)) {
return false;
}
Mytype my = (Mytype)o;
return (field == my.field ||(my.field != null &&field.equals(my.field)));
}
public boolean equals(Mytype o) {
}
}
有時(shí)候?qū)崊⑴c對(duì)象本身比較也省略掉了锌俱。
改寫hashCode方法
在每個(gè)改寫equals方法的類中,你也必須改寫hashCode()方法敌呈。
hashcode的約定:
- 如果一個(gè)對(duì)象的equals方法作比較所用的信息沒有被修改的話贸宏,那么,對(duì)該對(duì)象調(diào)用hashcode方法多次磕洪,始終返回用一個(gè)整數(shù)值吭练。
- 如果兩個(gè)對(duì)象根據(jù)equals方法是相等的,那么它們具有相同的散列碼
- 如果兩個(gè)對(duì)象根據(jù)equals方法不是相等的析显,那么調(diào)用這兩個(gè)對(duì)象中任一個(gè)對(duì)象的hashcode方法鲫咽,不要求必須產(chǎn)生不同的整數(shù)結(jié)果。
未重寫hashcode方法引發(fā)的問題
如果一個(gè)類A只重寫了equals方法,當(dāng)它的實(shí)例對(duì)象被用作HashMap的鍵時(shí)分尸,在獲取這個(gè)鍵對(duì)應(yīng)的值的時(shí)候锦聊,問題來了,用put(K1,V)方法和get(K1)方法時(shí)箩绍,這里涉及類A的兩個(gè)實(shí)例孔庭,第二個(gè)對(duì)象和第一個(gè)實(shí)例相等,第二個(gè)實(shí)例用于檢索時(shí)材蛛,問題發(fā)生了圆到,類A并沒有重寫hashcode方法,所以兩個(gè)實(shí)例具有不同的散列碼卑吭,違反了hashcode的約定芽淡,因此,Put方法把對(duì)象K1放在一個(gè)散列桶里陨簇,而get方法去另一個(gè)散列桶里查找他的對(duì)象。
改寫hashcode的"部分良方"
以下是從《Effective Java》截取的一部分良方迹淌,如果想看更詳細(xì)的方案河绽,可以參閱《Effective Java》這本書。
- 把數(shù)值17(或者其他數(shù)值)保存在一個(gè)result的int類型的變量中
- 計(jì)算int類型的散列碼
- boolean類型計(jì)算:(f ? 0:1)
- 以下是例子:
@Override
public int hashCode() {
int result = 17;
result = result * 37 + string.hashCode();
result = result * 37 + number;
return result;
}