java5 引入的新的枚舉類型一直沒怎么注意,以為和其他語言一樣,就是一個代替常量來保證安全使用的基本數據結構。
今天剛翻完Effective java 2nd,才注意到java 的枚舉不一樣的地方。

和其他語言不同的地方是,java的枚舉本身一個標準的java類,只是不能擴展,構造器也不可以直接訪問。

enum 的構造器,可以顯示的聲明,用來給enum的 instance變量賦值或者做一些操作。自身也可以使用方法
比如下面的代碼段。

 IL_DOTS(0);         // dots inlays

    
/** The upcharge for the feature */

    
private float upcharge;


    
private GuitarFeatures(float upcharge) {

    
this.upcharge = upcharge;

    }



      其中IL_DOTS 就是調用了構造器,并給自身的實例變量upcharge 進行了扶植。


這樣除了替換一般的常量使用的話,還可以結合java類自身的特點,比如接口,方法等,以更加安全的方式把枚舉做類進行使用。


作者給出了多個條款,闡述如何利用枚舉類型來實現類型安全的代碼編寫。下面是條款細節和自己做的一些筆記
 
Item 3: Enforce the singleton property with a private constructor or an enum type 。
使用枚舉的特性來實現單件模式。 枚舉自身的構造器是私有方式的,可以通過簡單的方式就實現單件模式

a single-element enum type is the best way to implement a singleton.

枚舉本身的特性使得他非常適合設計成singleton類。下面代碼是一個以枚舉方式實現的singleton類, 作者認為這種singleton模式實現方式是最好的
參看下面代碼中singleton的實現。 INSTANCE代表了枚舉的一個實例。

 

public enum Elvis {
        INSTANCE;
        
public void leaveTheBuilding() {
                System.out.println(
"this is a joke");
        }

        
        
public static void main(String[] args) {
                Elvis.INSTANCE.leaveTheBuilding();
        }

}



和使用utility方式的差別是不需要把方法都聲明為static的,在語法上會更加靈活.


item 30: Use enums instead of int constants

使用枚舉來代替整形常量, 好處除了類型安全以為, 還因為枚舉本身也是java類,可以提供方法,有助于實現一些復雜的行為。
缺點是在性能上比int有所下降。

 

Item 31: Use instance fields instead of ordinals
不要直接使用枚舉中的求序號(ordinals)方式來獲取綁定值,應該使用實例變量來代替。

enum提供一個ordinal方法來返回返回枚舉常量的序數(它在枚舉聲明中的位置,其中初始常量序數為零)。
但是此方法一般只在一些通用的數據結構中使用, 比如EnumSet 和EnumMap。

對此方法的一種誤用就是期望用次序號來和常量進行簡單的綁定,由此可以得到某常量的一個偽值
比如
public enum Ensemble {
        SOLO, DUET, TRIO, QUARTET, QUINTET, SEXTET, SEPTET, OCTET, NONET, DECTET;
        public int numberOfMusicians() {
                return ordinal() + 1;
        }
}

但是這種做法比較危險,因為常量數字有可能發生變化,既然enum也是類,所以應該使用類的instance field來完成這種值綁定行為。

public enum Ensemble {
        SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5), SEXTET(6), SEPTET(7), OCTET(
                        8), DOUBLE_QUARTET(8), NONET(9), DECTET(10), TRIPLE_QUARTET(12);
        private final int numberOfMusicians;

        Ensemble(int size) {
                this.numberOfMusicians = size;
        }

        public int numberOfMusicians() {
                return numberOfMusicians;
        }
}
Item 32: Use EnumSet instead of bit fields
使用EnumSet代替位類型字段。 位類型字段常見于一些需要做異或操作疊加的屬性, 比如字體,樣式等等。

public class Text {
   public static final int STYLE_BOLD          = 1 << 0; // 1
   public static final int STYLE_ITALIC        = 1 << 1; // 2
   public static final int STYLE_UNDERLINE     = 1 << 2; // 4
   public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8
  // Parameter is bitwise OR of zero or more STYLE_ constants
  public void applyStyles(int styles) { ... }
}

text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

這樣使用的樣式就是bold和itatlic的交集

正如item30 提到的使用 enum來代替 常量int一樣,此處可以使用enum和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) { ... }
}

text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
EnumSet的of返回的是一個有2個Style的集合,然后再在方法applyStyles中進行處理即可。

這里面要注意的是,實際對舊有的代碼無法做此種改造,比如java2d里面的字體和樣式設定等等。 此處的推薦更多應該是放在
設計新的框架代碼時使用。

我個人覺得這條用處不大,使用非bit方式的編碼, 有助于提高代碼的可讀性,但是一般程序員多少都應該有此概念,就不是大問題了,而既然舊的代碼無法改,兩種方式混合用,恐怕頭更大。

Item 33: Use EnumMap instead of ordinal indexing
使用EnumMap代替使用枚舉常量的序號進行操作

比如如下代碼, 使用序號來完成映射操作,是比較常見的技巧。

public enum Phase { SOLID, LIQUID, GAS;
   public enum Transition { MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT;
           private static final Transition[][] TRANSITIONS = {
              { null,    MELT,     SUBLIME },
              { FREEZE,  null,     BOIL    },
              { DEPOSIT, CONDENSE, null    }
           };
          // Returns the phase transition from one phase to another
          public static Transition from(Phase src, Phase dst) {
             return TRANSITIONS[src.ordinal()][dst.ordinal()];
          }
   }
}


這種做法的問題在于編譯器無法準確知道 TRANSITIONS的映射關系,所以無法保證代碼的正確性。
而使用enummap,其中的key值是必須在enum中的,就可以靜態的保證正確性。
public enum Phase {
        SOLID, LIQUID, GAS;
        public enum Transition {
                MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID), BOIL(LIQUID, GAS), CONDENSE(
                                GAS, LIQUID), SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);
                final Phase src;
                final Phase dst;

                Transition(Phase src, Phase dst) {
                        this.src = src;
                        this.dst = dst;
                }

                // Initialize the phase transition map
                private static final Map<Phase, Map<Phase, Transition>> m
                        = new EnumMap<Phase, Map<Phase, Transition>>(
                                Phase.class);
                static {
                        for (Phase p : Phase.values())
                                m.put(p, new EnumMap<Phase, Transition>(Phase.class));
                        for (Transition trans : Transition.values())
                                m.get(trans.src).put(trans.dst, trans);
                }

                public static Transition from(Phase src, Phase dst) {
                        return m.get(src).get(dst);
                }
        }
}

不過我個人覺得這種代碼只適合寫安全性要求較高的框架級別代碼,否則寫的這么啰嗦,煩死了。使用數組來映射,用UT來保證類型安全也是一樣的,代碼可讀性更好。

Item 34: Emulate extensible enums with interfaces
可以使用使用接口的方式來模擬枚舉的擴展,
這是因為枚舉類被設計成為final的,原則上無法進行繼承方式的擴展。

此處以操作碼做列子進行了講解。
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;
                }
        },
        MINUS("-") {
                public double apply(double x, double y) {
                        return x - y;
                }
        },
        TIMES("*") {
                public double apply(double x, double y) {
                        return x * y;
                }
        },
        DIVIDE("/") {
                public double apply(double x, double y) {
                        return x / y;
                }
        };
        private final String symbol;

        BasicOperation(String symbol) {
                this.symbol = symbol;
        }

        @Override
        public String toString() {
                return symbol;
        }
}

此處BasicOperation不能被直接擴展,可以依賴接口來實現一個擴展的enum
//Emulated extension enum
public enum ExtendedOperation implements Operation {
    EXP("^") {
        public double apply(double x, double y) {
            return Math.pow(x, y);
        }
    },
    REMAINDER("%") {
        public double apply(double x, double y) {
            return x % y;
        }
    };
    private final String symbol;
    ExtendedOperation(String symbol) {
        this.symbol = symbol;
    }
    @Override public String toString() {
        return symbol;
    }
}

因為兩個enum都實現了Operation接口, 那么當然也就可以通過接口來統一操作

        public static void main(String[] args) {
                double x = Double.parseDouble(args[0]);
                double y = Double.parseDouble(args[1]);
                test(Arrays.asList(ExtendedOperation.values()), x, y);
        }

        private static void test(Collection<? extends Operation> opSet, double x,
                        double y) {
                for (Operation op : opSet)
                        System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
        }
此處傳遞ExtendOperation或者BasicOperation都可以, 也可以合并在一起傳。


按作者的說法,這種實現的一個主要缺點就是既然不是真正的繼承結構,所以會有一些代碼冗余,可以通過幫助類來解決。

我覺得作者有點,算了,還是不侮辱我偶像了, 這個例子的前提是enum設計使用了統一的接口,也就是說如果想擴展別人做好的東西,那是不行的, 而自己的東西這么設計,目的可能是為以后留一個擴展,而調用的代碼就復雜了不少,這樣的需求,我還是傾向于直接使用一個類結構來解決。


作者對enums的使用,整了一些比較復雜的方法,主要是基于java的枚舉本身也是一種特殊的類來考慮的,但是個人覺得這些做法反而增加了代碼的復雜度,強調編譯期安全的問題,其實可以由UT來解決,還有助于培養團隊成員的良好工作習慣。

當然了,讀完這部分內容,對java枚舉會有更好的認識。

感覺新版的Effective java處處都在強調如何做到類型安全, 另外對java 5的內容關注的非常多,介紹了一些比較復雜的技巧和用法,算是長了不少見識,就是有些過于啰嗦了,不知道是不是人年紀太大了。