java5 引入的新的枚舉類(lèi)型一直沒(méi)怎么注意,以為和其他語(yǔ)言一樣,就是一個(gè)代替常量來(lái)保證安全使用的基本數(shù)據(jù)結(jié)構(gòu)。
今天剛翻完Effective java 2nd,才注意到j(luò)ava 的枚舉不一樣的地方。
和其他語(yǔ)言不同的地方是,java的枚舉本身一個(gè)標(biāo)準(zhǔn)的java類(lèi),只是不能擴(kuò)展,構(gòu)造器也不可以直接訪問(wèn)。
enum 的構(gòu)造器,可以顯示的聲明,用來(lái)給enum的 instance變量賦值或者做一些操作。自身也可以使用方法
比如下面的代碼段。
IL_DOTS(0); // dots inlays


/** *//** The upcharge for the feature */

private float upcharge;



private GuitarFeatures(float upcharge)
{

this.upcharge = upcharge;

}


其中IL_DOTS 就是調(diào)用了構(gòu)造器,并給自身的實(shí)例變量upcharge 進(jìn)行了扶植。
這樣除了替換一般的常量使用的話,還可以結(jié)合java類(lèi)自身的特點(diǎn),比如接口,方法等,以更加安全的方式把枚舉做類(lèi)進(jìn)行使用。
作者給出了多個(gè)條款,闡述如何利用枚舉類(lèi)型來(lái)實(shí)現(xiàn)類(lèi)型安全的代碼編寫(xiě)。下面是條款細(xì)節(jié)和自己做的一些筆記
Item 3: Enforce the singleton property with a private constructor or an enum type 。
使用枚舉的特性來(lái)實(shí)現(xiàn)單件模式。 枚舉自身的構(gòu)造器是私有方式的,可以通過(guò)簡(jiǎn)單的方式就實(shí)現(xiàn)單件模式
a single-element enum type is the best way to implement a singleton.
枚舉本身的特性使得他非常適合設(shè)計(jì)成singleton類(lèi)。下面代碼是一個(gè)以枚舉方式實(shí)現(xiàn)的singleton類(lèi), 作者認(rèn)為這種singleton模式實(shí)現(xiàn)方式是最好的
參看下面代碼中singleton的實(shí)現(xiàn)。 INSTANCE代表了枚舉的一個(gè)實(shí)例。

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的,在語(yǔ)法上會(huì)更加靈活.
item 30: Use enums instead of int constants
使用枚舉來(lái)代替整形常量, 好處除了類(lèi)型安全以為, 還因?yàn)槊杜e本身也是java類(lèi),可以提供方法,有助于實(shí)現(xiàn)一些復(fù)雜的行為。
缺點(diǎn)是在性能上比int有所下降。
Item 31: Use instance fields instead of ordinals
不要直接使用枚舉中的求序號(hào)(ordinals)方式來(lái)獲取綁定值,應(yīng)該使用實(shí)例變量來(lái)代替。
enum提供一個(gè)ordinal方法來(lái)返回返回枚舉常量的序數(shù)(它在枚舉聲明中的位置,其中初始常量序數(shù)為零)。
但是此方法一般只在一些通用的數(shù)據(jù)結(jié)構(gòu)中使用, 比如EnumSet 和EnumMap。
對(duì)此方法的一種誤用就是期望用次序號(hào)來(lái)和常量進(jìn)行簡(jiǎn)單的綁定,由此可以得到某常量的一個(gè)偽值
比如
public enum Ensemble {
SOLO, DUET, TRIO, QUARTET, QUINTET, SEXTET, SEPTET, OCTET, NONET, DECTET;
public int numberOfMusicians() {
return ordinal() + 1;
}
}
但是這種做法比較危險(xiǎn),因?yàn)槌A繑?shù)字有可能發(fā)生變化,既然enum也是類(lèi),所以應(yīng)該使用類(lèi)的instance field來(lái)完成這種值綁定行為。
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代替位類(lèi)型字段。 位類(lèi)型字段常見(jiàn)于一些需要做異或操作疊加的屬性, 比如字體,樣式等等。
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來(lái)代替 常量int一樣,此處可以使用enum和enumSet結(jié)合來(lái)完成同樣的工作
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返回的是一個(gè)有2個(gè)Style的集合,然后再在方法applyStyles中進(jìn)行處理即可。
這里面要注意的是,實(shí)際對(duì)舊有的代碼無(wú)法做此種改造,比如java2d里面的字體和樣式設(shè)定等等。 此處的推薦更多應(yīng)該是放在
設(shè)計(jì)新的框架代碼時(shí)使用。
我個(gè)人覺(jué)得這條用處不大,使用非bit方式的編碼, 有助于提高代碼的可讀性,但是一般程序員多少都應(yīng)該有此概念,就不是大問(wèn)題了,而既然舊的代碼無(wú)法改,兩種方式混合用,恐怕頭更大。
Item 33: Use EnumMap instead of ordinal indexing
使用EnumMap代替使用枚舉常量的序號(hào)進(jìn)行操作
比如如下代碼, 使用序號(hào)來(lái)完成映射操作,是比較常見(jiàn)的技巧。
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()];
}
}
}
這種做法的問(wèn)題在于編譯器無(wú)法準(zhǔn)確知道 TRANSITIONS的映射關(guān)系,所以無(wú)法保證代碼的正確性。
而使用enummap,其中的key值是必須在enum中的,就可以靜態(tài)的保證正確性。
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);
}
}
}
不過(guò)我個(gè)人覺(jué)得這種代碼只適合寫(xiě)安全性要求較高的框架級(jí)別代碼,否則寫(xiě)的這么啰嗦,煩死了。使用數(shù)組來(lái)映射,用UT來(lái)保證類(lèi)型安全也是一樣的,代碼可讀性更好。
Item 34: Emulate extensible enums with interfaces
可以使用使用接口的方式來(lái)模擬枚舉的擴(kuò)展,
這是因?yàn)槊杜e類(lèi)被設(shè)計(jì)成為final的,原則上無(wú)法進(jìn)行繼承方式的擴(kuò)展。
此處以操作碼做列子進(jìn)行了講解。
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不能被直接擴(kuò)展,可以依賴(lài)接口來(lái)實(shí)現(xiàn)一個(gè)擴(kuò)展的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;
}
}
因?yàn)閮蓚€(gè)enum都實(shí)現(xiàn)了Operation接口, 那么當(dāng)然也就可以通過(guò)接口來(lái)統(tǒng)一操作
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都可以, 也可以合并在一起傳。
按作者的說(shuō)法,這種實(shí)現(xiàn)的一個(gè)主要缺點(diǎn)就是既然不是真正的繼承結(jié)構(gòu),所以會(huì)有一些代碼冗余,可以通過(guò)幫助類(lèi)來(lái)解決。
我覺(jué)得作者有點(diǎn),算了,還是不侮辱我偶像了, 這個(gè)例子的前提是enum設(shè)計(jì)使用了統(tǒng)一的接口,也就是說(shuō)如果想擴(kuò)展別人做好的東西,那是不行的, 而自己的東西這么設(shè)計(jì),目的可能是為以后留一個(gè)擴(kuò)展,而調(diào)用的代碼就復(fù)雜了不少,這樣的需求,我還是傾向于直接使用一個(gè)類(lèi)結(jié)構(gòu)來(lái)解決。
作者對(duì)enums的使用,整了一些比較復(fù)雜的方法,主要是基于java的枚舉本身也是一種特殊的類(lèi)來(lái)考慮的,但是個(gè)人覺(jué)得這些做法反而增加了代碼的復(fù)雜度,強(qiáng)調(diào)編譯期安全的問(wèn)題,其實(shí)可以由UT來(lái)解決,還有助于培養(yǎng)團(tuán)隊(duì)成員的良好工作習(xí)慣。
當(dāng)然了,讀完這部分內(nèi)容,對(duì)java枚舉會(huì)有更好的認(rèn)識(shí)。
感覺(jué)新版的Effective java處處都在強(qiáng)調(diào)如何做到類(lèi)型安全, 另外對(duì)java 5的內(nèi)容關(guān)注的非常多,介紹了一些比較復(fù)雜的技巧和用法,算是長(zhǎng)了不少見(jiàn)識(shí),就是有些過(guò)于啰嗦了,不知道是不是人年紀(jì)太大了。