就簡單的應用而言,上一個小節介紹的列舉型態入門,就比舊版本的常數設定方式多了編譯時期型態檢查的好處,然而列舉型態的功能還不止這些,這個小節中再介紹更多列舉型態的定義方式,您可以將這個小節介紹的內容當作另一種定義類別的方式,如此可以幫助您理解如何定義列舉型態。
定義列舉型態時其實就是在定義一個類別,只不過很多細節由編譯器幫您補齊了,所以某些程度上 "enum" 關鍵字的作用就像是 "class" 或 "interface"。
當您使用 "enum" 定義列舉型態時,實際上您所定義出來的型態是繼承自 java.lang.Enum 類別,而每個被列舉的成員其實就是您定義的列舉型態的一個實例,它們都被預設為 "final",所以您無法改變常數名稱所設定的值,它們也是 "public" 且 "static" 的成員,所以您可以透過類別名稱直接使用它們。
舉個實際的例子,範例 11.3 定義了 Action 列舉型態,當中定義的 TURN_LEFT、TURN_RIGHT、SHOOT 都是 Action 的一個物件實例,因為是物件,所以物件上自然有一些方法可以呼叫使用,例如從 Object 繼承下來的 toString() 方法被重新定義了,可以讓您直接取得列舉值的字串描述;values () 方法可以讓您取得所有的列舉成員實例,並以陣列方式傳回,您可以使用這兩個方法來簡單的將 Action(要使用範例 11.3)的列舉成員顯示出來。
public class ShowEnum {
public static void main(String[] args) {
for(Action action: Action.values()) {
System.out.println(action.toString());
}
}
}
基本上 println() 會自動呼叫物件 toString(),所以不寫 toString() 其實也是可以的,執行結果如下:
TURN_LEFT
TURN_RIGHT
SHOOT
由於每一個列舉的成員都是一個物件實例,所以您可以使用 "==" 或是 equals() 方法來比較列舉物件,"==" 會比較您提供的列舉物件是不是同一個物件,而 equals() 則是實質的比較兩個列舉物件的內容是否相等,使用 equals() 時預設會根據列舉物件的字串值來比較。
靜態 valueOf() 方法可以讓您將指定的字串嘗試轉換為列舉實例,您可以使用 compareTo() 方法來比較兩個列舉物件在列舉時的順序,範例是這兩個方法的實際例子。
public class EnumCompareTo {
public static void main(String[] args) {
compareToAction(Action.valueOf(args[0]));
}
public static void compareToAction(Action inputAction) {
System.out.println("輸入:" + inputAction);
for(Action action: Action.values()) {
System.out.println(action.compareTo(inputAction));
}
}
}
compareTo() 如果傳回正值,表示設定為引數的列舉物件(inputAction)其順序在比較的列舉物件(action)之前,負值表示在之後,而0則表示兩個互比列舉值的位置是相同的,執行結果如下:
java EnumCompareTo SHOOT
輸入:SHOOT
-2
-1
0
對於每一個列舉成員,您可以使用 ordinal() 方法,依列舉順序得到位置索引,預設以 0 開始,範例 11.8 是個簡單示範。
public class EnumIndex {
public static void main(String[] args) {
for(Action action : Action.values()) {
System.out.printf("%d %s%n", action.ordinal(), action);
}
}
}
執行結果:
0 TURN_LEFT
1 TURN_RIGHT
2 SHOOT
定義列舉型態基本上就像是在定義類別,定義列舉型態時您也可以定義方法,例如,您也許會想要為列舉值加上一些描述,而不是使用預設的 toString() 返回值來描述列舉值,如範例 11.9 所示。
public enum DetailAction {
TURN_LEFT, TURN_RIGHT, SHOOT;
public String getDescription() {
switch(this.ordinal()) {
case 0:
return "向左轉";
case 1:
return "向右轉";
case 2:
return "射擊";
default:
return null;
}
}
}
您可以使用範例 11.10 來測試一下所定義的方法是否有用。
public class DetailActionDemo {
public static void main(String[] args) {
for(DetailAction action : DetailAction.values()) {
System.out.printf("%s:%s%n",
action, action.getDescription());
}
}
}
執行結果:
TURN_LEFT:向左轉
TURN_RIGHT:向右轉
SHOOT:射擊
列舉型態既然是類別,那麼您可以為它加上建構方法(Constructor)嗎?答案是可以的,但是不得為公開的(public)建構方法,這是為了避免粗心的程式設計人員直接對列舉型態實例化,一個不公開的建構方法可以作什麼?來看看下面範例 11.11 的實作。
public enum DetailAction2 {
TURN_LEFT("向左轉"), TURN_RIGHT("向右轉"), SHOOT("射擊");
private String description;
// 不公開的建構方法
private DetailAction2(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
在列舉 TURN_LEFT、TURN_RIGHT、SHOOT 成員時,您可以一併指定文字描述,這個描述會在建構列舉物件時使用,範例 11.11 中您將之設定給私用成員 description,在使用 getDescription() 時將之返回,您可以使用範例 11.10 加以修改(將DetailAction改為DetailAction2),可以得到相同的顯示結果。
在定義列舉值時也可以一併實作介面(Interface),例如先來定義一個介面。
public interface IDescription {
public String getDescription();
}
您可以使用這個介面規定每個實作該介面的列舉,都必須傳回一個描述列舉值的字串,如範例 11.13 所示。
public enum DetailAction3 implements IDescription {
TURN_LEFT("向左轉"), TURN_RIGHT("向右轉"), SHOOT("射擊");
private String description;
// 不公開的建構方法
private DetailAction3(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
良葛格的話匣子 非公開的建構方法最常見的例子就是「Singleton 模式」的應用,當某個類別只能有一個實例時,可由類別維護唯一的實例,這時可以將建構方法設定為私用(private),取用此類別的開發人員就不能自行新增多個實例了,可以看看我網站上有關 Singleton模式的介紹:
因值而異的類實作?原文為 Value-Specific Class Bodies,其實這個功能簡單的說,實作時像是在使用「匿名內部類別」(Anonymous inner class)(9.1.2 有介紹) 來實現「Command 模式」,它讓您可以為每個列舉值定義各自的類本體與方法(Method)實作。
先來看看其中一種實現的方式,這邊要使用範例 11.12 的 IDescription 介面,您希望每個列舉的實例實作自己的 getDescription() 方法(而不是像範例 11.13 所介紹的,在定義列舉時實作一個統一的 getDescription() 方法),如範例 11.14 所示。
public enum MoreAction implements IDescription {
TURN_LEFT {
// 實作介面上的方法
public String getDescription() {
return "向左轉";
}
}, // 記得這邊的列舉值分隔使用 ,
TURN_RIGHT {
// 實作介面上的方法
public String getDescription() {
return "向右轉";
}
}, // 記得這邊的列舉值分隔使用 ,
SHOOT {
// 實作介面上的方法
public String getDescription() {
return "射擊";
}
}; // 記得這邊的列舉值結束使用 ;
}
每個列舉成員的 '{' 與 '}' 之間是類本體,您還可以在當中如同定義類別一樣的宣告資料成員或實作方法。TURN_LEFT、TURN_RIGHT 與 SHOOT 三個 MoreAction 的列舉實例各自在本體(Body),也就是 '{' 與 '}' 之間實作了自己的 getDescription() 方法,而不是像範例 11.13 中統一實作在 DetailAction3 中,使用範例 11.15 作個測試。
public class MoreActionDemo {
public static void main(String[] args) {
for(MoreAction action : MoreAction.values()) {
System.out.printf("%s:%s%n",
action, action.getDescription());
}
}
}
這個例子是將「因值而異的類實作」用在返回列舉值描述上,您可以依相同的方式,為每個列舉值加上一些各自的方法實作,而呼叫的介面是統一的,執行結果會顯示各自的列舉描述:
TURN_LEFT:向左轉
TURN_RIGHT:向右轉
SHOOT:射擊
您也可以運用抽象方法來改寫範例 11.14,如範例 11.16 所示。
public enum MoreAction2 {
TURN_LEFT {
// 實作抽象方法
public String getDescription() {
return "向左轉";
}
}, // 記得這邊的列舉值分隔使用 ,
TURN_RIGHT {
// 實作抽象方法
public String getDescription() {
return "向右轉";
}
}, // 記得這邊的列舉值分隔使用 ,
SHOOT {
// 實作抽象方法
public String getDescription() {
return "射擊";
}
}; // 記得這邊的列舉值結束使用 ;
// 宣告個抽象方法
public abstract String getDescription();
}
MoreAction2 與 MoreAction 不同的地方在於 MoreAction2 是實作抽象方法,您可以改寫一些範例 11.15(將 MoreAction 改為 MoreAction2),而執行結果是一樣的;基本上定義介面方法或抽象方法,是為了知道物件的操作介面,這樣您才能去操作這個物件。