列舉(enum) 為一組有限集合(固定的常量)的類型,每個列舉成員都為靜態 final 的特徵。
因為列舉為單例(Singleton),因此外部無法創造列舉的實例,也不能進行擴展。
有以下幾種常用的情況
1.使用 enum 取代狀態常量
狀態常量通常為一組相似的常量用來表示某種狀態,並根據這些常量來判斷進行動作。e.g.
public class ErrorHandler { public static final int ALARM_ARRAY_DATA_ERROR = 1; public static final int SET_ALARM_ARRAY_ERROR = 2; public static final int OUT_OF_BOUNDARY_ERROR = 3; ... private void checkErrorCode(int errorCode){ switch (errorCode) { case ErrorHandler.ALARM_ARRAY_DATA_ERROR: ... break; case ErrorHandler.SET_ALARM_ARRAY_ERROR: ... break; case ErrorHandler.OUT_OF_BOUNDARY_ERROR: ... break; } } }
第3到5行定義一組代表不同的錯誤狀態常量,並在第9行的方法中判斷錯誤狀態並進行相對動作。
這種狀態常量稱為(int enum pattern),具有幾種缺點。
1. 無法百分之百確定傳入switch的數值範圍處於定義的範圍之內。
2. 若想印出錯誤狀態,必須另外再寫方法。
使用 enum 取代 狀態常量。e.g.
public class ErrorHandler{ ... public enum ErrorType{ ALARM_ARRAY_DATA_ERROR, SET_ALARM_ARRAY_ERROR, OUT_OF_BUNDARY_ERROR } ... public void checkErrorCode(ErrorType errorType){ switch (errorType) { case ALARM_ARRAY_DATA_ERROR: ... break; case SET_ALARM_ARRAY_ERROR: ... break; case OUT_OF_BOUNDARY_ERROR: ... break; } } ... }
enum 的命名跟隨 class 的命名,大寫開頭,其成員由於具有 static final 的特性故使用全大寫加上底線命名。
如果 ErrorType 只會被使用在 ErrorHandler 中,存取權限為 private。若會被多個 class 使用就獨立為一個 public 類別。
2.將方法或是屬性加到 enum。
用於想讓 enum 的成員具有其他資訊,ErrorType 也具有數值的表示。
public enum ErrorType{ ALARM_ARRAY_DATA_ERROR(0), SET_ALARM_ARRAY_ERROR(1), OUT_OF_BUNDARY_ERROR(2); private int mNumber; private ErrorType(int number){ mNumber = number; } public int getNumber(){ return mNumber; } }
3.常用的 enum 方法。
public void showAllEnumMembersByValues() { for(ErrorHandler.ErrorType errorType: ErrorHandler.ErrorType.values()){ System.out.println("errorType.toString():"+errorType.toString()); } } public static void checkMemberNameInEnum(){ ErrorHandler.ErrorType.valueOf(ErrorHandler.ErrorType.ALARM_ARRAY_DATA_ERROR.toString()); ErrorHandler.ErrorType.valueOf("no this member"); }
第2行 values() 會依照宣告的順序傳回 enum 的成員們。
第3行 toString() 預設的情況下會回傳成員本身名稱。
第8及第9行 valuesOf() 會檢查傳入的字串是否符合成員的名稱,是的話回傳該成員,否的話丟出 illegalArgumentException。
因此第9行會丟出例外。
4.應該使用列舉的狀況為需要一組固定常量的時候,也包含真實世界的現象,如星期的天數,月份等等。
5.不要使用 ordinal() 來取得 enum 成員的順序。
ordinal() 會回傳每個 enum 成員在該 enum 中的位置,但若改變其他成員的順序,ordinal() 回傳值會不如預期。
如果想要確實的取得 enum 成員的相關數值,那就另外新增一個屬性並用該屬性來保存(參考第2點)。
6.若有相同的方法,但每個 enum member 的實現都各有不同,可以新增 abstract method 再讓各 member 去實現。
public enum ErrorType { ALARM_ARRAY_DATA_ERROR(0) { @Override String getNote() { return "alarm error because..."; } }, SET_ALARM_ARRAY_ERROR(1) { @Override String getNote() { return "set alarm array error because ..."; } }, OUT_OF_BUNDARY_ERROR(2) { @Override String getNote() { return "out of bundary error because..."; } }; private int mNumber; private ErrorType(int number) { mNumber = number; } abstract String getNote(); public int getNumber() { return mNumber; } }
第27行的 getNote() 對於每個 enum member 來說都不相同,我們把它設定為 abstract 並讓 member 去分別實作它。
7. EnumSet 的使用,EnumSet 的使用方式和一般的 Set 沒有甚麼不同。
差別在於 EnumSet 只能接受 enum 的成員加入,優點是EnumSet 的效率相當高。
public void testEnumSet(){ EnumSet<ErrorHandler.ErrorType> errorTypes = EnumSet.noneOf(ErrorHandler.ErrorType.class); errorTypes.add(ErrorType.ALARM_ARRAY_DATA_ERROR); errorTypes.addAll(EnumSet.of(ErrorType.ALARM_ARRAY_DATA_ERROR, ErrorType.OUT_OF_BUNDARY_ERROR)); errorTypes.remove(ErrorType.ALARM_ARRAY_DATA_ERROR); }
第2行 EnumSet 建構式比較特別,參數必須指定 enum class。
8. EnumMap 的使用與 EnumSet 類似,其鍵值必須來自 enum 的 member。
其他的使用方式就如同一般的 Map。
EnumMap<ErrorHandler.ErrorType , String > errorTypes = new EnumMap<ErrorHandler.ErrorType, String>(ErrorHandler.ErrorType.class); errorTypes.put(ErrorType.ALARM_ARRAY_DATA_ERROR, ErrorType.ALARM_ARRAY_DATA_ERROR.toString()); System.out.println("errorTypes.get(ErrorType.ALARM_ARRAY_DATA_ERROR):"+errorTypes.get(ErrorType.ALARM_ARRAY_DATA_ERROR)); errorTypes.clear();
第1行建構式必須指定 enum class。