Effective Java讀書筆記二:列舉和註解(30-37)

衣舞晨風發表於2017-02-04

第30條:用enum代替int常量

當需要一組固定常量的時候,應該使用enum代替int常量,除了對於手機登資源有限的裝置應該酌情考慮enum的效能弱勢之外。

第31條:用例項域代替序數

列舉的ordinal()方法會返回列舉常量在型別中的數字位置, 但是儘量不要使用它,因為當重新排序後,會對客戶端造成破壞。 正確的做法是,將他儲存在一個例項域中。

應該給enum新增int域,而不是使用ordinal方法來匯出與列舉關聯的序數值。(幾乎不應使用ordinal方法,除非在編寫像EnumMap這樣的基於列舉的通用資料結構)

//WRONG
public enum Fruit{
    APPLE, PEAR, ORANGE;
    public int numberOfFruit(){
        return ordinal() + 1;
   }
}

//RIGHT
public enum Fruit{
    APPLE(1), PEAR(2), ORANGE(3);
    private final int number;
    Fruit(int num) {number  = num;}
    public int numberOfFruit(){
        return number;
    }
}

第32條:用EnumSet代替位域

每個EnumSet的內容都表示為位向量。如若底層的列舉型別個數小於64個,則整個EnumSet就用單個long來表示,因此效能上比的上位域。

//WRONG
public class Text{
    private static final int STYLE_BOLD                  = 1 << 0;
    private static final int STYLE_ITALIC                 = 1 << 1;
    private static final int STYLE_UNDERLINE         = 1 << 2;

    public void applyStyles(int styles) {...}
}
//use
text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

//RIGHT
public class Text{
    public enum Style{STYLE_BOLD, STYLE_ITALIC, STYLE_UNDERLINE}

    public void applyStyles(Set<Style> styles) {...} //這裡不使用EnumSet<Style>引數是因為考慮到某些客戶端可能會傳遞一些其他的Set實現
}
//use
text.applyStyles(EnumSet.of(STYLE_BOLD, STYLE_ITALIC));
  1. EnumSet 實現了 Set 介面,提供了豐富的功能,型別安全.可以從其他任何Set中得到互換性.
  2. 整個 EnumSet 就是用 單個 long 來表示的,效能上比得上 位運算的效能.
  3. 總而言之因為列舉型別要用在集合(Set)中,所以沒有理由用位域來表示.

第33條:用EnumMap代替序數索引

序數索引是指依賴於列舉成員在列舉中的序數來進行陣列索引,如:

//定義了植物類,其中植物又分為水果,蔬菜,樹木三種
public class Plant{
    public enum Type { Fruit, Vegetables, Tree}
    private final String name;
    private final Type type;

    Plant(String name, Type type){
        this.name = name;
        this.type = type;
    }
}

Set<Plant>[] plants = (Set<Plant>[]) new Set[Plant.Type.valuse().lenght]; 
//根據植物的型別,分別把所有的植物放入三個set中
for(int i = 0; i < plant.lenght; i++){
   plant[i] = new HashSet<Plant>();
}

for(Plant p : garden){  //garden裡放了所有的植物
    plant[p.type.ordinal()].add(p)  //反面教材:利用了列舉的序數來得到想要的陣列索引,使用者在其他地方可以不使用ordinal函式,而直接使用int值來訪問,就可能出錯
}

應該使用EnumMap來實現,EnumMap內部是採用陣列實現的,具有Map的豐富功能和型別安全以及陣列 的效率:

Map<Plant.Type, Set<Plant>> plants = new EnumMap<Plant.Type, Set<Plant>>(Plant.Type.class);  //建構函式需要 鍵 型別的Class物件
//根據植物的型別,分別把所有的植物放入三個set中
for(Plant.Type type : Plant.Type.valuse()){
   plant.put(type, new HashSet<Plant>);
}

for(Plant p : garden){  //garden裡放了所有的植物
    plant.get(p.type).add(p)  //使用者必須使用正確的鍵值來訪問,即Type型別
}

當需要多維關係時,可以使用EnumMap<…, EnumMap<…>>

第34條:用介面模擬可以伸縮的列舉

1、如果讓一個 列舉型別 去擴充套件另一個 列舉型別,利用語言的特性,幾乎是不可能的
2、列舉的可擴充套件性,到最後都證明不是一個好點子.

由於在java中enum不是可擴充套件的,在某些情況下,可能需要對列舉進行擴充套件,比如操作型別(+-*/等),就可以考慮:
1.定義一個介面,比如public interface Operation{…};
2.使列舉繼承介面:比如public enum BasicOperation implements Operation{…}
3.使用時的API寫成介面(比如,T extends Enum & Operation),而不是實現(比如BasicOperation )

private static <T extends Enum<T> & Operation> void function(T t,..); //表示T即表示列舉又是Operation的子型別

4.當需要擴充套件BasicOperation列舉時,就可以另寫一個列舉,且implements介面Operation

第35條:註解優先於命名模式

優先使用註解來表面針對某些程式元素的特定資訊。
註解的優勢:

  1. @Retention : 限定保留時期
  2. @Target: 限定其應用的程式元素
  3. 還有很多註解,如 @IntDef,@ViewDebug…
  4. 註解接收的引數如果是陣列,為其賦值一個單獨的元素也是合法的

第36條:堅持使用Override註解

在想要覆蓋的方法上使用Override註解,編譯器就可以幫助發現一些錯誤。可以不寫Override的特例:在具體類中不必標註你確信覆蓋了抽象方法宣告的方法(雖然這麼做也沒有什麼壞處)。

第37條:用標記介面實現型別

標記分為標記介面和標記註解。

標記介面:沒有包含方法宣告的介面,只是指明某個類實現了具有某種屬性的介面。比如Serializable介面,通過實現這個介面,類表明它的例項可以被寫到ObjectOutputStream。。

標記介面與標記註解的最終要的區別在於:
標記介面可以在編譯時就檢查到相應的型別問題,而標記註解則要到執行時。

標記介面優勢:

  1. 標記介面定義的型別是由 被標記的類的例項 實現的,標記註解 則沒有定義這樣的型別
  2. 他們可以被更加精確的進行鎖定,比如
    如果註解型別利用 @Target(ElementType.TYPE) 標記,則它可以被應用到任何類或者介面上

標記註解優勢:

  1. 它可以 通過預設的方式 新增 一個或者多個註解型別元素,給 已被使用的註解型別新增更多的資訊.隨著時間的推移,簡單的標記註解型別可以演變成更加豐富的註解型別.
  2. 他們是更大的註解機制的一部分.

使用:

  1. 如果標記是應用到任何程式元素而不是類或者介面,就必須使用註解. 因為只有 類和介面可以用來實現或者擴充套件介面
  2. 如果標記只應用給類和介面,就應該 優先使用標記介面而非註解

《Effective Java中文版 第2版》PDF版下載:
http://download.csdn.net/detail/xunzaosiyecao/9745699

作者:jiankunking 出處:http://blog.csdn.net/jiankunking

相關文章