標籤:effective java
於Java 1.5增加的enum type.
enum type是由一組固定的常量組成的類型,比如四個季節、撲克花色。
在出現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;public static final int ORANGE_NAVEL = 0;public static final int ORANGE_TEMPLE = 1;public static final int ORANGE_BLOOD = 2;
如果只是想用作枚舉,感覺這樣也沒什麼。
但如果把上面的蘋果和橘子互作比較,或者寫成....
int i = (APPLE_FUJI - ORANGE_TEMPLE) / APPLE_PIPPIN;
雖合法但詫異,這是在做果汁嗎?
而且,這種常量是compile-time常量,編譯後一切都結束了,使用這個常量的地方都被替換為該常量的值。
如果該常量值需要改變,所有使用該常量的代碼都必須重新編譯。
更糟糕的情況是,不重新編譯也可以正常運行,只不過會得到無法預測的結果。
(ps:我覺得更遭的是有人直接把常量值寫到代碼裡...)
另外,比如上面的APPLE_FUJI,我想列印它的名字,不是它的值。
不僅如此,我還想列印所有蘋果,我想列印蘋果一共有多少種類。
當然,如果想列印也可以,只是相比直接使用enum,無論怎麼做都很麻煩。
如果使用enum,比如:
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH } public enum Orange { NAVEL, TEMPLE, BLOOD }
看起來就是一堆常量,但是enum沒有執行個體,也沒有可訪問的構造器,無法對其進行擴充。
enum本身就是final,所以很多時候也直接用enum實現singleton。
enum在編譯時間是型別安全的,比如有地方聲明了上面代碼中的Apple類型的參數,那麼被傳到該參數的引用肯定是三種蘋果之一。
而且enum本身就是一個類型,可以有自己的方法和field,而且可以實現介面。
附上書中太陽系enum,很難想象如果有類似需求時用普通常量來實現。
也許我可以聲明一個Planet類,再給它加上field的方法,然後在一個constant類中聲明為final
但這樣卻無法保證Planet類僅用作常量,所以還是用enum吧:
public enum Planet {MERCURY(3.302e+23, 2.439e6), VENUS(4.869e+24, 6.052e6), EARTH(5.975e+24,6.378e6), MARS(6.419e+23, 3.393e6), JUPITER(1.899e+27, 7.149e7), SATURN(5.685e+26, 6.027e7), URANUS(8.683e+25, 2.556e7), NEPTUNE(1.024e+26,2.477e7);private final double mass; // In kilogramsprivate final double radius; // In metersprivate final double surfaceGravity; // In m / s^2// Universal gravitational constant in m^3 / kg s^2private static final double G = 6.67300E-11;// ConstructorPlanet(double mass, double radius) {this.mass = mass;this.radius = radius;surfaceGravity = G * mass / (radius * radius);}public double mass() {return mass;}public double radius() {return radius;}public double surfaceGravity() {return surfaceGravity;}public double surfaceWeight(double mass) {return mass * surfaceGravity; // F = ma}}
然後我們就可以這樣使用Planet enum,無論是值還是名字,使用起來都很自然:
public class WeightTable { public static void main(String[] args) { double earthWeight = Double.parseDouble(args[0]); double mass = earthWeight / Planet.EARTH.surfaceGravity(); for (Planet p : Planet.values()) System.out.printf("Weight on %s is %f%n",p, p.surfaceWeight(mass)); }}
其實像Planet這樣的方式對多數使用枚舉的情境而言足夠了。
也就是說每個Planet常量表達的是不同的資料,但也有例外。
比如,我們要為enum中的每一個常量賦予不同的行為。
下面是書中用enum表達計算的例子:
import java.util.HashMap;import java.util.Map;public enum Operation {PLUS("+") {double apply(double x, double y) {return x + y;}},MINUS("-") {double apply(double x, double y) {return x - y;}},TIMES("*") {double apply(double x, double y) {return x * y;}},DIVIDE("/") {double apply(double x, double y) {return x / y;}};private final String symbol;Operation(String symbol) {this.symbol = symbol;}@Overridepublic String toString() {return symbol;}abstract double apply(double x, double y);private static final Map<String, Operation> stringToEnum = new HashMap<String, Operation>();static { for (Operation op : values())stringToEnum.put(op.toString(), op);}public static Operation fromString(String symbol) {return stringToEnum.get(symbol);}public static void main(String[] args) {double x = Double.parseDouble(args[0]);double y = Double.parseDouble(args[1]);for (Operation op : Operation.values())System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));}}
對不同的枚舉常量進行switch..case..其實也能表達出我們想要的效果。
如果以後增加了新的常量則需要再對應加上一個case,當然,不加也不會有任何提示,然後最壞的情況就是運行時出了問題。
如上面的代碼是常量行為的正確使用方法,即constant-specific method implementation。
為行為提供一個抽象,並為每一個常量提供一個實現,即一個枚舉常量也是constant-specific class body。
採用這種方式時,如果新增一個常量,則必須提供一個方法實現,否則編譯器會給出提示,這就多了一層保障。
遺憾的是,這種方式也有缺陷。
比如我們有這樣一個需求,計算某一天的薪水,這個某一天可以是一周中的某一天,也可能是某個節日,比如周一到周五使用相同的運算方式,周末另算,某節日另算。
也就是說我需要在枚舉中聲明代表周一到周日的常量,如果我繼續使用之前的方式去聲明一個抽象方法,如果周一到周五採用完全一樣的計算,則會出現五段完全相同的代碼。
但即使這樣我們也不能用回switch..case..方式,增加一個常量時強制選擇其選擇一種行為實現是必須的。
於是我們有一種叫strategy enum的方式,即枚舉中聲明另外一個枚舉的field,該field則代表策略,並提供策略相關的行為。
下面是書中代碼:
enum PayrollDay {MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY), WEDNESDAY(PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY), FRIDAY(PayType.WEEKDAY), SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND);private final PayType payType;PayrollDay(PayType payType) {this.payType = payType;}double pay(double hoursWorked, double payRate) {return payType.pay(hoursWorked, payRate);}private enum PayType {WEEKDAY {double overtimePay(double hours, double payRate) {return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT)* payRate / 2;}},WEEKEND {double overtimePay(double hours, double payRate) {return hours * payRate / 2;}};private static final int HOURS_PER_SHIFT = 8;abstract double overtimePay(double hrs, double payRate);double pay(double hoursWorked, double payRate) {double basePay = hoursWorked * payRate;return basePay + overtimePay(hoursWorked, payRate);}}}
本文出自 “Alvez. on 99.9% 0B/s” 部落格,請務必保留此出處http://alvez.blog.51cto.com/7711135/1536173