Effective Java Note(對(duì)于所有對(duì)象都通用的方法)

對(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

  1. 使用==檢查是待比較參數(shù)是否是同意對(duì)象引用本身
  2. 使用instanceof檢查參數(shù)是否是正確類型
  3. 把參數(shù)轉(zhuǎn)換成正確的類型,使用instanceof判斷
  4. 檢查參數(shù)中域與該對(duì)象中的對(duì)應(yīng)域相匹配。
  5. 除float 、double外的基本類型使用==比較
  6. 對(duì)象引用域可以遞歸調(diào)用equals
  7. 對(duì)于float,double分別使用Float.compare(),Double.compare()
  8. 數(shù)組判等使用Arrays.equals()。
  9. 對(duì)于允許null的域,盡可能避免NullPointException,可以做判空操作
  10. 對(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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容