Java SE 6 技術手冊

11.1 常數設置與列舉型態

在瞭解 J2SE 5.0 中新增的「列舉型態」(Enumerated Types)功能之前,您可以先瞭解一下在 J2SE 1.4 或之前版本中,是如何定義共用常數的,如此在接觸列舉型態時,您就可以感覺它所帶來的更多好處。

11.1.1 常數設置

有時候您會需要定義一些常數供程式使用,您可以使用介面或類別來定義,例如您可以使用介面來定義操作時所需的共用常數,範例 11.1 是個簡單示範。

範例 11.1 ActionConstants.java

public interface ActionConstants { 
    public static final int TURN_LEFT = 1; 
    public static final int TURN_RIGHT = 2; 
    public static final int SHOT = 3; 
}

共用的常數通常是可以直接取用並且不可被修改的,所以您在宣告時加上 "static" 與 "final",如此您可以在程式中直接使用像是 ActionConstants.TURN_LEFT 的名稱來取用常數值,例如:

public void someMethod() {
    ....
    doAction(ActionConstants.TURN_RIGHT);
    ....
}
public void doAction(int action) { 
    switch(action) { 
        case ActionConstants.TURN_LEFT: 
            System.out.println("向左轉"); 
            break; 
        case ActionConstants.TURN_RIGHT: 
            System.out.println("向右轉"); 
            break; 
        case ActionConstants.SHOOT: 
            System.out.println("射擊"); 
            break; 
    } 
}

如果使用類別來宣告的話,方法也是類似,例如:

範例 11.2 CommandTool.java

public class CommandTool {
    public static final String ADMIN = "onlyfun.caterpillar.admin";
    public static final String DEVELOPER = "onlyfun.caterpillar.developer";

    public void someMethod() {
        // ....
    }
}

如果常數只是在類別內部使用的話,就宣告其為 "private" 或是 "protected" 就可以了,宣告為類別外可取用的常數,通常是與類別功能相依的常數,例如使用 CommandTool 時若會使用到與 CommandTool 功能相依的常數的話,將這些常數直接宣告在 CommandTool 類別上取用時就很方便,而使用介面所宣告的常數,則通常是整個程式或某個模組中都會共用到的常數。

對於簡單的常數設置,上面的作法已經足夠了,在 J2SE 5.0 中則新增了「列舉型態」(Enumerated Types),使用列舉型態,除了簡單的常數設定功能之外,您還可以獲得像編譯時期型態檢查等更多的好處。

良葛格的話匣子 宣告常數時,通常使用大寫字母,並可以底線來區隔每個單字以利識別,例如像TURN_LEFT這樣的名稱。

11.1.2 列舉型態入門

您已經知道可以在類別或介面中宣告常數來統一管理常數,這只是讓您存取與管理常數方便而已,來看看這個例子:

public void someMethod() {
     ....
    doAction(ActionConstants.TURN_RIGHT);
    ....
}
public void doAction(int action) { 
    switch(action) { 
        case ActionConstants.TURN_LEFT: 
            System.out.println("向左轉"); 
            break; 
        .. 
    } 
}

這種作法本身沒錯,只不過 doAction() 方法接受的是int型態的常數,您沒有能力阻止程式設計人員對它輸入 ActionConstants 規定外的其它常數,也沒有檢查 "switch" 中列舉的值是不是正確的值,因為參數 action 就只是 int 型態而已,當然您可以自行設計一些檢查動作,這需要一些額外的工作,如果您使用 J2SE 5.0 中新增的「列舉型態」(Enumerated Types),就可以無需花額外的功夫就輕易的解決這些問題。

在 J2SE 5.0 中要定義列舉型態是使用 "enum" 關鍵字,以下先來看看列舉型態的應用,舉個實際的例子,範例 11.3 是定義了 Action 列舉型態。

範例 11.3 Action.java

public enum Action {
    TURN_LEFT, 
    TURN_RIGHT, 
    SHOOT
}

不用懷疑,在 Action.java 中撰寫範例 11.3 的內容然後編譯它,雖然語法上不像是在定義類別,但列舉型態骨子裏就是一個類別,所以您編譯完成後,會產生一個 Action.class 檔案。

來看下面範例 11.4 瞭解如何使用定義好的列舉型態。

範例 11.4 EnumDemo.java

public class EnumDemo {
    public static void main(String[] args) {
        doAction(Action.TURN_RIGHT);
    }

    public static void doAction(Action action) {
        switch(action) { 
            case TURN_LEFT: 
                System.out.println("向左轉"); 
                break; 
            case TURN_RIGHT: 
                System.out.println("向右轉"); 
                break; 
            case SHOOT: 
                System.out.println("射擊"); 
                break; 
        } 
    }
}

執行結果:

向右轉

除了讓您少打一些字之外,這個範例好像沒有什麼特別的,但注意到 doAction() 參數列的型態是 Action,如果您對 doAction() 方法輸入其它型態的引數,編譯器會回報錯誤,因為 doAction() 所接受的引數必須是 Action 列舉型態。 使用列舉型態還可以作到更進一步的檢驗,如果您在 "switch" 中加入了不屬於 Action 中列舉的值,編譯器也會回報錯誤,例如:

...
    public static void doAction(Action action) {
        switch(action) { 
            case TURN_LEFT: 
                System.out.println("向左轉"); 
                break; 
            case TURN_RIGHT: 
                System.out.println("向右轉"); 
                break; 
            case SHOOT: 
                System.out.println("射擊"); 
                break; 
            case STOP: // Action中沒有列舉這個值
                System.out.println("停止"); 
                break;
        } 
    } ...

在編譯時編譯器會替您作檢查,若檢查出不屬於 Action 中的列舉值,會顯示以下的錯誤:

unqualified enumeration constant name required 
case STOP:
^

您可以在一個獨立的檔案中宣告列舉值,或是在某個類別中宣告列舉成員,例如範例 11.5 將 Action 列舉型態宣告於 EnumDemo2 類別中。

範例 11.5 EnumDemo2.java

public class EnumDemo2 {
    private enum InnerAction {TURN_LEFT, TURN_RIGHT, SHOOT};

    public static void main(String[] args) {
        doAction(InnerAction.TURN_RIGHT);
    }

    public static void doAction(InnerAction action) {
        switch(action) { 
            case TURN_LEFT: 
                System.out.println("向左轉"); 
                break; 
            case TURN_RIGHT: 
                System.out.println("向右轉"); 
                break; 
            case SHOOT: 
                System.out.println("射擊"); 
                break; 
        } 
    }
}

執行結果:

向右轉

由於列舉型態本質上還是個類別,所以範例11.5的列舉宣告方式有些像在宣告「內部類別」(Inner class),在您編譯完 EnumDemo2.java 檔案後,除了 EnumDemo2.class 之外,您會有一些額外的 .class 檔案產生,在這個例子中就是 EnumDemo2$InnerAction.class 與 EnumDemo2$1.class,看到這兩個檔案,您就應該瞭解實際上編譯器產生了「成員內部類別」以及「匿名內部類別」(第 9 章說明過內部類別)。