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的內容關注的非常多,介紹了一些比較復雜的技巧和用法,算是長了不少見識,就是有些過于啰嗦了,不知道是不是人年紀太大了。