對(duì)于所有對(duì)象都通用的方法
1. 覆蓋equals時(shí)的通用約定
equals所期望的結(jié)果
- 類的每個(gè)實(shí)例本質(zhì)上都是唯一的。
- 不關(guān)心類是否提供了“邏輯相等”的測(cè)試功能。
- 超類已經(jīng)覆蓋了equals,從超類繼承過(guò)來(lái)的行為對(duì)于子類也是合適的。
- 類時(shí)私有的或者包私有的,可以確定它的equals方法永遠(yuǎn)不會(huì)調(diào)用。
需要覆蓋的時(shí)機(jī):
父類沒(méi)有實(shí)現(xiàn)所期望的以上的equals實(shí)現(xiàn)
"最多只存在一個(gè)對(duì)象"的類不需要覆蓋equals,例如枚舉類型
equals的等價(jià)關(guān)系
-
自反性。任何非null引用值,x.equals(x)返回true。
Set集合中重復(fù)添加同意一個(gè)引用值會(huì)怎樣?
-
對(duì)稱性。對(duì)于任何非null的x和y,x.equals(y) 返回true,那么y.equals(x) 也返回true。
自定義類A實(shí)現(xiàn)了不區(qū)分大小寫(xiě)的比較,new A("Aa").equals("aa")返回true,但是“aa”.equals(new A("Aa"))的返回值卻由String類中的equals方法決定。
-
傳遞性。對(duì)于任何非null的x,y,z,如果x.equals(y) 為true,且 y.equals(z) 也為true,那么x.equals(z) 也為true。
存在擴(kuò)展可實(shí)例化與增加主鍵值的時(shí)候,容易違反傳遞性??紤]使用抽象類并在其子類中添加屬性,例如Shape。
-
一致性。對(duì)于任何非null引用值x,y,只要x.equals(y)返回true,那么在引用對(duì)象信息沒(méi)有被修改,那么每次返回的仍是一致的true。
要保證一致性,不要使equals依賴于不可靠的的資源。例如java..net.URL的equals依賴于主機(jī)的IP,IP可能隨著時(shí)間的推移而改變。
對(duì)于任何非null引用值x,x.equals(null)必須返回false。
高質(zhì)量equals
- 使用==檢查是待比較參數(shù)是否是同意對(duì)象引用本身
- 使用instanceof檢查參數(shù)是否是正確類型
- 把參數(shù)轉(zhuǎn)換成正確的類型,使用instanceof判斷
- 檢查參數(shù)中域與該對(duì)象中的對(duì)應(yīng)域相匹配。
- 除float 、double外的基本類型使用==比較
- 對(duì)象引用域可以遞歸調(diào)用equals
- 對(duì)于float,double分別使用Float.compare(),Double.compare()
- 數(shù)組判等使用Arrays.equals()。
- 對(duì)于允許null的域,盡可能避免NullPointException,可以做判空操作
- 對(duì)個(gè)域比較的時(shí)候,或者比較比較步驟較多的時(shí)候,比較順序從最有可能不一致的開(kāi)始
注意點(diǎn)
- 覆蓋equals的時(shí)候總要覆蓋hashCode
- 不要讓equals過(guò)于智能。過(guò)度的尋求某種不必要的等價(jià)關(guān)系。
- 不要將equals中參數(shù)轉(zhuǎn)換成其他類型。某些情況能增加性能,但是比較復(fù)雜性會(huì)增加(不推薦)。
//本應(yīng)該是:
public void equals(Object o){
....
}
//轉(zhuǎn)換參數(shù)成其他類型:
public void equals(MyClass o){
....
}
//加上@Override導(dǎo)致變異不通過(guò),因?yàn)楦割惖膃quals方法不存在此重寫(xiě)方法。
@Override
public void equals(MyClass o){
....
}
2.覆蓋equals時(shí)總要覆蓋hashCode
JavaSE6中的Object規(guī)范
- 程序執(zhí)行期間,對(duì)象的equals方法所用到的比較信息沒(méi)有被修改,對(duì)同一個(gè)對(duì)象的多次調(diào)用hashCode始終如一的返回同一個(gè)整數(shù),但是多次執(zhí)行的過(guò)程中,所返回的整數(shù)可以不一致(信息修改后)。
- 兩個(gè)對(duì)象的equals返回true,那么這兩個(gè)對(duì)象的hashCode返回的整數(shù)必須相等。(所以覆蓋equals總要覆蓋hashCode)
- 兩個(gè)對(duì)象的equals返回false,那么不要求兩個(gè)對(duì)象的hashCode返回不一樣的整數(shù)結(jié)果,但是在使用hash的相關(guān)集合框架中,返回不一樣的結(jié)果能提高性能
HashMap,HashSet等hash集合框架,依賴元素的hashCode進(jìn)行散列,因此當(dāng)equals返回true的時(shí)候,hashCode返回的結(jié)果也一樣,可以保證是一個(gè)相等的元素。在put ,get,remove等操作均會(huì)使用到元素的hashCode。因此hashcode能影響到HashMap等Hash結(jié)合框架的性能。
計(jì)算hashcode的一些方法:
- boolean 計(jì)算(f?1:0)
- byte,char,short,int 計(jì)算(int)f
- long 計(jì)算(int)(f^(f>>>32))
- float 計(jì)算Float.floatToInBits(f);
- double 計(jì)算Double.doubleToLongBits(f),得到的結(jié)果再按long類型處理
- 對(duì)象引用 null則返回0,否則遞歸調(diào)用hashCode
- 數(shù)組 用以上規(guī)則計(jì)算每個(gè)元素的hashCode,或則使用Arrays.hashCode()
最后返回將上述計(jì)算得到的結(jié)果c,(result = 31 * result +c),返回 result。
注意:散列碼計(jì)算過(guò)程中排除掉冗余域,也就是沒(méi)有參與到equals中的域
上述使用到了31進(jìn)行最后結(jié)果的處理,因?yàn)?1有一個(gè)很好的特性是可以使用移位和減法來(lái)代替乘法。31*i = (i<<5)-i 而且VM會(huì)自動(dòng)完成這種優(yōu)化。
此外散列碼的計(jì)算可能是開(kāi)銷很大的,可以考慮懶加載,既是只在第一次調(diào)用hashCode的時(shí)候進(jìn)行計(jì)算,然后結(jié)果保存在實(shí)例中,下次直接返回保存的結(jié)果。但是前提是類是不可變的。
3.始終覆蓋toString
Object默認(rèn)的toString返回的結(jié)果是:類名@xxxxxx,其中xxxxxx是該對(duì)象的散列碼的十六進(jìn)制表示
覆蓋toString方法可以在調(diào)試或者打印對(duì)象信息的時(shí)候更易于閱讀理解。
但是在該類被廣泛使用的時(shí)候要保證toString的返回格式的一致性,可利于維護(hù)。
4.謹(jǐn)慎覆蓋clone
在JavaSE6中的clone方法的通用約定:
| x.clone()!=x | true |
|---|---|
| x.clone().getClass() == x.getClass() | true(不絕對(duì)要求如此) |
| x.clone().equals(x) | true(不絕對(duì)要求如此) |
?
Object中的clone方法是protected的,而一個(gè)類實(shí)現(xiàn)了Cloneable接口改變了clone的行為,是的clone可以返回一個(gè)該對(duì)象得到逐域拷貝對(duì)象,使得不通過(guò)構(gòu)造器就可以生成一個(gè)對(duì)象,否則拋出CloneNotSupportedExcetion。
如果覆蓋了非final類的clone方法,則應(yīng)該返回一個(gè)通過(guò)super.clone()得到的對(duì)象。而對(duì)于實(shí)現(xiàn)了Cloneable接口的類,如果所有超類否提供了良好的clone實(shí)現(xiàn),那么我們可以在實(shí)現(xiàn)了Cloneable的子類實(shí)現(xiàn)一個(gè)共有的clone的方法,否則我們不應(yīng)該提供任何clone實(shí)現(xiàn)。
當(dāng)需要clone的類中存在引用類型且不是final的域的時(shí)候,我們?nèi)孕枰{(diào)用該引用對(duì)象的clone方法進(jìn)行clone。
class Wheel implements Cloneable{
int size;
int count;
@Override
public Wheel clone(){
return (Wheel)super.clone();
}
}
class Car implements Cloneable{
Wheel wheel;
String name;
@Override
public Car clone(){
try{
Car car = (Car)super.clone();
car.wheel = wheel.clone();//省略這一行,那么car.wheel == this.wheel(淺拷貝)
return car;
}catch(CloneNotSupportedException e){
throw new AssertionError();
}
}
}
另一種情況,存在引用鏈的情況下,我們需要遞歸的進(jìn)行復(fù)制
public class HashTable implements Cloneable{
private Entry[] buckets = .....;
private static class Entry{
final Object key;
Object value;
Entry next;
Entry(Object key,Object value,Entry e){
....
}
public deepCopy(){
//遞歸調(diào)用可能會(huì)棧溢出
return new Entry(key,value,next==null?null:next.deepCopy());
//使用迭代
Entry e = new Entry(key,value,next);
for(Entry p = e;p.next!=null;p=p.next)
p.next = new Entry(key,value,p.next.next);
return e;
}
}
@Override
public HashTable clone(){
try{
HashTable ht =(HashTable)super.clone();
//這樣得到的buckets中的元素與this的buckets中的元素時(shí)指向同一個(gè)對(duì)象的(淺拷貝)
//ht.buckets = buckets.clone();
//單獨(dú)拷貝每個(gè)buckets中的鏈表元素(深拷貝)
ht.buckets = new Entry[buckets.length];
for(int i=0,s=buckets.lenght;i<s;i++){
if(buckets[i]!=null){
ht.buckets[i] = buckets[i].deepCopy();
}
}
return ht;
}catch(CloneNotSupportedException e){
throw new AssertionError();
}
}
}
多線程環(huán)境霞需要自己實(shí)現(xiàn)同步的clone
其他實(shí)現(xiàn)對(duì)象拷貝的方法:
拷貝構(gòu)造函器
public Car(Car car){
.....
}
靜態(tài)工廠拷貝方法
public static Car newInstance(Car car){
......
}
Java中的使用例子:
基于接口的拷貝:Collection, Map。HashSet hs = new HashSet(); TreeSet ts = new TreeSet(hs);
4.考慮實(shí)現(xiàn)Comparable接口
comparable接口屬于一個(gè)泛型接口,一個(gè)類實(shí)現(xiàn)了該接口可以和許多依賴該接口的集合實(shí)現(xiàn)協(xié)作功能,很明顯的一個(gè)功能就是內(nèi)在的排序關(guān)系。
Comparable接口中待實(shí)現(xiàn)的的方法是compareTo(T t)。返回正整數(shù),0,負(fù)整數(shù)來(lái)表示當(dāng)前對(duì)象大于,等于,小于參數(shù)對(duì)象。
compareTo同樣應(yīng)該滿足:自反性,傳遞性,對(duì)稱性
compareTo返回0是,同樣應(yīng)該滿足equals返回true
compareTo需要比較的值域較多是,從最右可能產(chǎn)生不一致的域開(kāi)始比較,依次類推
對(duì)于float double類型的比較,使用Float.compare() ,Double.compare();
compareTo與equals的差別是compareTo是參數(shù)化的,不必進(jìn)行參數(shù)轉(zhuǎn)化,此外其他的很多equals中的特點(diǎn)同樣適用與compareTo。