Effective Java : 枚舉和註解

30.使用enum代替int常量

以前的方案

在枚舉出現前,都是 使用常量的方式,如

public static final int APPLE_FUJI = 0;  
public static final int ORANGE_NAVEL = 0;

這種方稱爲枚舉常量,其弊端有:

  • 如果與枚舉常量關聯的 int 發生變化,則必須重新編譯
  • 如果將枚舉常量翻譯成可打印的字符串,只能見到一個數字,沒有太大的用處.

枚舉方式

java 中的枚舉本質上是 int值.

public enum Apple{FUJI}
public enum Apple{NAVEL}
  1. 枚舉類基本想法 : 通過公有的 final域爲 每個枚舉常量 導出實例的類.
  2. 因爲沒有可訪問的構造器, 因此枚舉類型都是 final
  3. 因爲 客戶端 無法創建 枚舉實例,也不能對其 進行擴展,因此枚舉是 實例受控的,單例的泛型化
  4. 編譯時類型安全,聲明後取值一定是 枚舉中的有效值 之一
  5. 通過複寫 toString,可以將 枚舉的值打印出來.
  6. 枚舉可以添加任意的方法和域,並實現任意的接口

枚舉的高級用法

  • switch,枚舉中可以通過 switch(this)來根據不同的 做不同的操作,

示例 : Operation.java

  • 當然,上面種方式並不好,建議使用 特定於常量的方法實現(onstant-specific method implementation) Operation1.java

  • 可以通過構造函數傳遞參數,例如,示例:Operation.java, op 打印是調用toString打印出了+,-,*,/;

  • 利用策略枚舉,可以用在更加安全,靈活的場景.如:書中的加班場景,每添加一種枚舉常量就強制添加一種策略,示例代碼:PayrollDay.java

總結

  1. switch 枚舉 適合於 給外部的 枚舉類型 增加特定於 常量的行爲.
  2. 一般來說,枚舉會 優先使用 comparable類型.而非 int類型
  3. 需要一組固定常量的時候就可以使用枚舉.
  4. 枚舉 裝載和初始化的時候會有 空間和時間 的成本.

31.用實例域代替序數

簡介

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

示例

 public enum Ensemble {
    SOLO(1), DUET(2), TRIO(3), QUARTET(4);

    private final int numberOf;

    Ensemble(int _i) {
      this.numberOf = _i;
    }

    public int getNumberOf() {
      return numberOf;
    }
  }

32.使用enumset代替位域

int 枚舉模式

public class Text {
    public static final int STYLE_BOLD = 1 << 0;
    public static final int STYLE_ITALIC = 1 << 1;
    public static final int STYLE_UNDERLINE = 1 << 2;
    public static final int STYLE_STRIKETHROUGH = 1 << 3;

    public void applyStyles(int styles) {
      //...
    }
  }

EnumSet模式

public class Text {
    public enum Style {
        BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
    }
    // Any Set could be passed in, but EnumSet is clearly best
    public void applyStyles(Set<Style> styles) {
        // Body goes here
    }
}
  1. EnumSet 實現了 Set 接口,提供了豐富的功能,類型安全.可以從其他任何Set中得到互換性.
  2. 整個 EnumSet 就是用 單個 long 來表示的,性能上比得上 位運算的性能.
  3. 總而言之因爲枚舉類型要用在集合(Set)中,所以沒有理由用位域來表示.

33.用enummap代替序數索引

簡介

這裏的總體原則 和上一個 一致, 就是 儘量不要使用 ordinal() 方法.
以枚舉 序數 作爲 數組索引 總不是那麼精確.

建議使用 EmumMap來索引數組,如果是 多維的 ,可以使用 Enum<...,Enum<?>>

書中 示例代碼: Herb.java

34.用接口模擬可伸縮的枚舉

簡介

  1. 如果讓一個 枚舉類型 去擴展另一個 枚舉類型,利用語言的特性,幾乎是不可能的/
  2. 枚舉的可擴展性,到最後都證明不是一個好點子.

接口模擬枚舉的伸縮性

鑑於如上兩點,我們可以利用枚舉定義接口操作碼類型來擴展枚舉,使枚舉具有可擴展性. 示例代碼: Operation.java

一.定義如下接口

public interface Operation {
    double apply(double x, double y);
}

二. 基本實現類

public enum BasicOperation implements Operation {
    PLUS("+") {
        public double apply(double x, double y) {
            return x + y;
        }
    },
//...
}

三. 擴展實現類型

public enum ExtendedOperation implements Operation {
    EXP("^") {
        public double apply(double x, double y) {
            return Math.pow(x, y);
        }
    },
//...
}

總結

  1. 從示例中可以學到 泛型繼承的更深層次用法,如
// test parameter is a bounded type token (Item 29)
    private static <T extends Enum<T> & Operation> void test(Class<T> opSet,
            double x, double y) {
        for (Operation op : opSet.getEnumConstants())
            System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
    }

確保了Class 既是枚舉類型,又是Operation的子類型

  1. 雖然無法編寫可擴展的枚舉類型,卻可以通過接口來模擬其伸縮性.
  2. 這裏唯一不足,如上的 保存和獲取 與某項操作相關聯的符號的邏輯代碼,仍無法複用,最好建立輔助類helper來操作.

35.註解優先於命名模式

簡介

1.5之前,Java使用的 是命名模式,如JUnit,這種模式有以下缺點:

  1. 拼寫錯誤不能及時發現
  2. 無法保證命名只在正確的場景使用
  3. 沒有值/類型信息,編譯器無法提前發現問題

使用 註解 可以很好的解決 如上問題,通過元註解進行約定

  1. @Retention : 限定保留時期
  2. @Target: 限定其應用的程序元素
  3. 還有很多註解,如 @IntDef,@ViewDebug
  4. 註解接收的參數如果是數組,爲其賦值一個單獨的元素也是合法的,如下
@ExceptionTest({
IndexOutOfBoundsException.class,NullPointerException.class
})

36.堅持使用override屬性

簡介

@override 註解表明,被註解的方法聲明覆蓋了超類中的方法聲明.
如果使用此註解,而不是 超類中的方法聲明,就會報編譯時錯誤

所以,最好是在 每個想要覆蓋超類的方法 聲明上添加 @override 註解

37.用標記接口定義類型

簡介

標記接口就是 不包含方法聲明的接口.指明一個實現類 具有 某種屬性
Serializable表明實現類 可以被寫到 ObjectOutputStream

第 35item 中瞭解過 標記註解 ,在這裏所說的是標記接口.

標記接口 相當於 標記註解 有如下特點

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

標記註解 勝過 標記接口 的地方

  1. 它可以通過 默認的方式 添加 一個或者多個註解類型元素,給 已被使用的註解類型添加更多的信息.隨着時間的推移,簡單的標記註解類型可以演變成更加豐富的註解類型.
  2. 他們是更大的註解機制的一部分.

何時使用註解,何時使用接口

  1. 如果標記是應用到任何程序元素而不是類或者接口,就必須使用註解. 因爲只有 類和接口可以用來實現或者擴展接口
  2. 如果標記只應用給類和接口,就應該 優先使用標記接口而非註解

總而言之

  1. 如果想要定義 一個任何新方法都不會與之關聯的類型,標記接口是最好的選擇
  2. 如果想要標記程序元素而非類和接口,考慮到未來可能要給標記添加更多信息,標記註解是更好的選擇.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章