天天看點

EffectiveJava(筆記五) 枚舉和注解30. 用enum代替int常量31. 用執行個體域代替序數32. EnumSet33. 用EnumMap代替序數索引34. 用接口模拟可伸縮的枚舉

30. 用enum代替int常量

枚舉類型(enum type)是指由一組固定的常量組成合法值的類型, 在程式設計語言中還沒有引入枚舉類型之前, 表示枚舉類型的床用模式是聲明一組具有名的int常量

public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
           

這種方法稱作int枚舉模式, 存在着諸多不足, 它在類型安全性和使用友善性沒有任何幫助, 采用int枚舉模式的程式是十分脆弱的, 因為int枚舉是編譯時常量, 被編譯到使用它們的用戶端中, 如果與枚舉常量關聯的int發生了變化, 用戶端就必須重新編譯

将int枚舉常量翻譯成可列印的字元串, 并沒有很便利的方法, 如果将這種常量列印出來, 或者從調試器中将它顯示出來, 就是一個數字, 并沒有太大用處

如果用String枚舉模式呢, 這種模式的常量提供了可列印的字元串, 但它會導緻性能問題, 因為它依賴于字元串的比較操作, 而Java的枚舉類型是功能十分齊全的類, 本質上是int值

包含同名常量的多個枚舉類型可以在一個系統中和平共處, 因為每個類型都有自己的命名空間, 可以增加或者重新排列枚舉類型中的常量, 而無需重新編譯它的用戶端代碼, 因為常量值并沒有被編譯到用戶端代碼中, 而是在int枚舉模式之中, 最終可以通過調用toString方法将枚舉轉換成可列印的字元串

處理完善int枚舉模式的不足之處, 枚舉類型還允許添加任意的方法和域, 并實作任意的接口, 它們提供了所有Object方法的進階實作, 實作了Comparable和Serializable接口, 并針對枚舉類型的可任意改變性設計了序列化方式

31. 用執行個體域代替序數

枚舉天生就與一個int值相關聯, 所有的枚舉都有一個ordinal方法, 它傳回每個枚舉常量在類型中的數字位置, 這雖然不錯, 但是維護起來就像一場噩夢, 如果常量進行重新排序, 對應的常量int值也會發生變化, 是以解決這種問題的方法是: 永遠不要根據枚舉的序數導出與它關聯的值, 而是要将它儲存在一個執行個體域中, 如:

public enum Ensemble {
    SOLO(1), DUET(2), TRIO(3);
    private final int value

    Ensemble(int value) {
        this.value = value;     
    }

    public int getValue() {
        return this.value;
    }
}
           

Enum規範中的ordinal: 大多數程式員都不需要這個方法, 它是設計成用于像EnumSet和EnumMap這種基于枚舉的通用資料結構的, 除非你在編寫的是這種資料結構, 否則最好完全避免使用ordinal方法

32. EnumSet

public class Text {
    public enum Style {BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }

    public void applyStyles(Set<Style> styles) { ... }
}

//EnumSet提供了豐富的靜态工廠來輕松建立集合
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
           

33. 用EnumMap代替序數索引

EnumMap是Map接口的實作, key-value中的key是Enum類型, 其原理就是一個對象數組, 數組的下标索引就是根據Map中的key直接擷取, 即擷取其中的ordinal值, 效率比HashMap高, 可以直接擷取數組下标索引并通路到元素, 與數組不同的是, EnumMap支援泛型, 而數組不支援

public enum Operate {
    ADD, UPDATE, DELETE;
}

//EnumMap初始化
Map<Operate, String> map = new EnumMap<Operate, String>(Operate.class);
//添加集合元素
map.put(Operate.DELETE, "delete operate");
//直接通過下标擷取value
map.get(Operate.DELETE);
//直接通過下标移除元素
map.remove(Operate.DELETE);
//key周遊擷取
for(Operate operate : map.keySet()) {       
}
//value周遊擷取
for(String obj : map.values()) {
}
//key-value周遊擷取
for(Entry<Operate, String> entry : map.entrySet()) {
}
           

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

雖然枚舉類型不是可擴充的, 但是接口類型則是可擴充的, 而enum類同樣也可以實作接口

public interface Operation {

double apply(double x, double y);

}

public enum BasicOperation implements Operation {
    PLUS("+") {
        @Override
        public double apply(double x, double y) {
            return x + y;
        }
    };
    private final String symbol;
    BasicOperation(String symbol) {
        this.symbol = symbol;
    }