Effective Item 15 - 枚舉

來源:互聯網
上載者:User

標籤: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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.