Enum(“枚舉”)全稱為 enumeration, 是 JDK 1.5 中引入的新特性,在Java開發者眼中枚舉已經是再普通不過的一種類型了,可以說任何Java程式員都編寫過自己的枚舉類,一般這些枚舉類用來表示同一類屬性,如:
Java代碼
- public enum OSType {
- Linux, MacOSX, Windows, iOS, Android, DOS;
- }
以上是一個表示作業系統類型的枚舉,在使用時我們隻需要選擇其中某種類型即可,如:OSType.Windows,這樣既簡單又友善。
枚舉的取值僅限于已定義的所有類型,以OSType為例,也就是說我們隻能選擇OSType中定義的這些作業系統類型,即使出現新的類型我們也無法使用,除非更新OSType。這種特性一方面可以了解為局限性,另一方面也可了解為限制性,限制使用者的目标選擇。
雖然枚舉我們經常會用到,但是,往往我們越是熟悉,越是覺得普通,平時根本不注意的事物,就越是不簡單。是以今天我們就一步步的去了解這個經常使用卻并不熟悉的“枚舉”。
1.枚舉的父類
定義一個新枚舉我們隻需要聲明“enum”關鍵字即可,雖說在開發過程中枚舉是一種類型,但最終“枚舉”會被編譯成一個class,通過javap 反編譯指令就可以證明這一點,在此之前我們來看一下javap 指令的使用說明:
Java代碼
- javap -help
- 用法: javap <options> <classes>
- 其中, 可能的選項包括:
- -help --help -? 輸出此用法消息
- -version 版本資訊
- -v -verbose 輸出附加資訊
- -l 輸出行号和本地變量表
- -public 僅顯示公共類和成員
- -protected 顯示受保護的/公共類和成員
- -package 顯示程式包/受保護的/公共類和成員 (預設)
- -p -private 顯示所有類和成員
- -c 對代碼進行反彙編
- -s 輸出内部類型簽名
- -sysinfo 顯示正在處理的類的系統資訊 (路徑, 大小, 日期, MD5 散列)
- -constants 顯示最終常量
- -classpath <path> 指定查找使用者類檔案的位置
- -cp <path> 指定查找使用者類檔案的位置
- -bootclasspath <path> 覆寫引導類檔案的位置
于是,我們使用javap反編譯上面的OSType枚舉,得到以下結果:
Java代碼
- javap OSType.class
- Compiled from "OSType.java"
- public final class OSType extends java.lang.Enum<OSType> {
- public static final OSType Linux;
- public static final OSType MacOSX;
- public static final OSType Windows;
- public static final OSType iOS;
- public static final OSType Android;
- public static final OSType DOS;
- public static OSType[] values();
- public static OSType valueOf(java.lang.String);
- static {};
- }
通過控制台輸出的反編譯結果可以看到,OSType 枚舉被編譯成了一個public final class OSType 并繼承自java.lang.Enum<OSType>,指定其泛型為OSType 。
是以,可以得知我們使用枚舉其實跟使用一個常量類并沒有什麼不同,但為什麼還要創造這個類型呢?帶着疑問我們進一步學習和分析。
雖然編譯後Enum 類是OSType 枚舉的父類,但在編寫代碼過程中我們卻無法使用extends 關鍵字令枚舉去繼承任何類,否則編譯器直接會給出錯誤提示。
反編譯結果中類OSType繼承了java.lang.Enum這個類,那麼我們就先打開java.lang.Enum 的源代碼來看一看,以下就是java.lang.Enum 的源代碼(JDK1.8版本,代碼中我已經将說明加上,有利于大家了解):
Java代碼
- package java.lang;
- import java.io.Serializable;
- import java.io.IOException;
- import java.io.InvalidObjectException;
- import java.io.ObjectInputStream;
- import java.io.ObjectStreamException;
- public abstract class Enum<E extends Enum<E>>
- implements Comparable<E>, Serializable {
- private final String name;
- public final String name() {
- return name;
- }
- private final int ordinal;
- public final int ordinal() {
- return ordinal;
- }
- protected Enum(String name, int ordinal) {
- this.name = name;
- this.ordinal = ordinal;
- }
- public String toString() {
- return name;
- }
- public final boolean equals(Object other) {
- return this==other;
- }
- public final int hashCode() {
- return super.hashCode();
- }
- protected final Object clone() throws CloneNotSupportedException {
- throw new CloneNotSupportedException();
- }
- public final int compareTo(E o) {
- Enum<?> other = (Enum<?>)o;
- Enum<E> self = this;
- if (self.getClass() != other.getClass() && // optimization
- self.getDeclaringClass() != other.getDeclaringClass())
- throw new ClassCastException();
- return self.ordinal - other.ordinal;
- }
- @SuppressWarnings("unchecked")
- public final Class<E> getDeclaringClass() {
- Class<?> clazz = getClass();
- Class<?> zuper = clazz.getSuperclass();
- return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
- }
- public static <T extends Enum<T>> T valueOf(Class<T> enumType,
- String name) {
- T result = enumType.enumConstantDirectory().get(name);
- if (result != null)
- return result;
- if (name == null)
- throw new NullPointerException("Name is null");
- throw new IllegalArgumentException(
- "No enum constant " + enumType.getCanonicalName() + "." + name);
- }
- protected final void finalize() { }
- private void readObject(ObjectInputStream in) throws IOException,
- ClassNotFoundException {
- throw new InvalidObjectException("can't deserialize enum");
- }
- private void readObjectNoData() throws ObjectStreamException {
- throw new InvalidObjectException("can't deserialize enum");
- }
- }
Enum 類中有兩個成員變量:name 與ordinal,分别用于表示該枚舉的名稱和排序位置。Enum 中還有一些常用的方法,作用及原理都比較簡單,我這裡就不詳細講解了。
2.枚舉中隐含的方法
編譯後的自定義枚舉類是java.lang.Enum 這個抽象類的子類,并且添加了valueOf(java.lang.String)和values()的方法。然而這兩個方法卻并不是源代碼中已經定義的,甚至你根本都找不到它是在哪裡實作的。
既然根本找不到實作,那麼我們就自己去重寫一下,于是在OSType枚舉類中試着重寫valueOf(String name)這個方法:
Java代碼
- //The enum OSType already defines the method valueOf(String) implicitly
- public OSType valueOf(String name) {
- }
重寫valueOf(String name)方法後卻驚奇的發現,編譯器給出了錯誤提示,意思是說:該枚舉(OSType)已經隐式定義了valueOf(String)方法,我們無法進行重寫,否則編譯器會給出錯誤提示。
原來在枚舉中已經定義了幾種方法,如:values()、valueOf(String),當重新定義它們時,編譯器會主動給出錯誤提示:
Java代碼
- public static E[] values();
- public static E valueOf(String name);
更詳細的内容可以參看:https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.9.2
3.枚舉的構造方法/函數
java.lang.Enum類中有一個唯一的構造函數:
Java代碼
- protected Enum(String name, int ordinal) {
- this.name = name;
- this.ordinal = ordinal;
- }
雖然Enum 類是枚舉的實際父類,但這也隻是在編譯後,在編譯前我們是無法使用Enum 的任何方法和構造參數的。
但是我們可以為自己定義的枚舉建立适合的構造參數,如:
Java代碼
- public enum OSType {
- Linux, MacOSX, Windows("windows"), iOS("ios", 3), Android(4), DOS();
- private int order;
- private String value;
- OSType(){
- }
- OSType(String value){
- this.value = value;
- }
- OSType(int order){
- this.order = order;
- }
- OSType(String value, int order){
- this.order = order;
- this.value = value;
- }
- public String getValue() {
- return value;
- }
- public void setValue(String value) {
- this.value = value;
- }
- public int getOrder() {
- return order;
- }
- public void setOrder(int order) {
- this.order = order;
- }
- }
自定義構造函數的好處就在于,我們可以為枚舉提供更豐富的“值/意義”。但是枚舉屬性的定義必須符合構造函數,這是為什麼呢?其實原理很簡單,在使用枚舉前,枚舉通過invokestatic 位元組碼關鍵字已經把所有枚舉屬性進行了初始化,初始化當然需要調用構造函數,是以枚舉屬性的定義就需要符合構造函數的定義規則。
通過javap -c 指令,輸出更多内容:
Java代碼
- javap -c OSType.class
- Compiled from "OSType.java"
- public final class OSType extends java.lang.Enum<OSType> {
- public static final OSType Linux;
- public static final OSType MacOSX;
- public static final OSType Windows;
- public static final OSType iOS;
- public static final OSType Android;
- public static final OSType DOS;
- public static OSType[] values();
- Code:
- 0: getstatic #1 // Field $VALUES:[LOSType;
- 3: invokevirtual #2 // Method "[LOSType;".clone:()Ljava/
- lang/Object;
- 6: checkcast #3 // class "[LOSType;"
- 9: areturn
- public static OSType valueOf(java.lang.String);
- Code:
- 0: ldc #4 // class OSType
- 2: aload_0
- 3: invokestatic #5 // Method java/lang/Enum.valueOf:(Lj
- ava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
- 6: checkcast #4 // class OSType
- 9: areturn
- ...
- static {};
- Code:
- 0: new #4 // class OSType
- 3: dup
- 4: ldc #9 // String Linux
- 6: iconst_0
- 7: invokespecial #10 // Method "<init>":(Ljava/lang/Strin
- g;I)V
- 10: putstatic #11 // Field Linux:LOSType;
- 13: new #4 // class OSType
- 16: dup
- 17: ldc #12 // String MacOSX
- 19: iconst_1
- 20: invokespecial #10 // Method "<init>":(Ljava/lang/Strin
- g;I)V
- 23: putstatic #13 // Field MacOSX:LOSType;
- 26: new #4 // class OSType
- 29: dup
- 30: ldc #14 // String Windows
- 32: iconst_2
- 33: ldc #15 // String windows
- 35: invokespecial #16 // Method "<init>":(Ljava/lang/Strin
- g;ILjava/lang/String;)V
- 38: putstatic #17 // Field Windows:LOSType;
- 41: new #4 // class OSType
- 44: dup
- 45: ldc #18 // String iOS
- 47: iconst_3
- 48: ldc #19 // String ios
- 50: iconst_3
- 51: invokespecial #20 // Method "<init>":(Ljava/lang/Strin
- g;ILjava/lang/String;I)V
- 54: putstatic #21 // Field iOS:LOSType;
- 57: new #4 // class OSType
- 60: dup
- 61: ldc #22 // String Android
- 63: iconst_4
- 64: iconst_4
- 65: invokespecial #23 // Method "<init>":(Ljava/lang/Strin
- g;II)V
- 68: putstatic #24 // Field Android:LOSType;
- 71: new #4 // class OSType
- 74: dup
- 75: ldc #25 // String DOS
- 77: iconst_5
- 78: invokespecial #10 // Method "<init>":(Ljava/lang/Strin
- g;I)V
- 81: putstatic #26 // Field DOS:LOSType;
- 84: bipush 6
- 86: anewarray #4 // class OSType
- 89: dup
- 90: iconst_0
- 91: getstatic #11 // Field Linux:LOSType;
- 94: aastore
- 95: dup
- 96: iconst_1
- 97: getstatic #13 // Field MacOSX:LOSType;
- 100: aastore
- 101: dup
- 102: iconst_2
- 103: getstatic #17 // Field Windows:LOSType;
- 106: aastore
- 107: dup
- 108: iconst_3
- 109: getstatic #21 // Field iOS:LOSType;
- 112: aastore
- 113: dup
- 114: iconst_4
- 115: getstatic #24 // Field Android:LOSType;
- 118: aastore
- 119: dup
- 120: iconst_5
- 121: getstatic #26 // Field DOS:LOSType;
- 124: aastore
- 125: putstatic #1 // Field $VALUES:[LOSType;
- 128: return
- }
其中invokespecial 關鍵字的作用就是調用指定構造函數進行初始化,跟Java代碼中的new Object() 形式類似。
4.枚舉中聲明方法
在定義的枚舉類中我們可以聲明方法(為了友善及節省篇幅,我把代碼整理了一下),如:
Java代碼
- public enum OSType {
- Linux("linux"), Windows("windows");
- // 聲明的方法
- public String getOSTypeName() {
- return value;
- }
- private String value;
- // 聲明的方法
- public static void main(String args[]) {
- System.out.println(OSType.Linux.getOSTypeName());
- System.out.println(OSType.Windows.getValue());
- }
- OSType(){
- }
- OSType(String value){
- this.value = value;
- }
- // 聲明的方法
- public String getValue() {
- return value;
- }
- // 聲明的方法
- public void setValue(String value) {
- this.value = value;
- }
- }
- //列印結果:
- linux
- windows
從列印結果可以看到我們調用枚舉中定義的public 方法成功了。需要注意的是,如果将方法定義成private我們将無法在外部對其進行調用。
在枚舉中我們亦可以定義抽象的方法體,但是定義了抽象方法後,必須在枚舉成員體中實作它,因為每一個枚舉成員你都可以了解成一個命名不同的類執行個體如:
Java代碼
- public enum OSType {
- Linux("linux") {
- @Override
- String getProducer() {
- return "Red Hat";
- }
- },
- Windows("windows") {
- @Override
- String getProducer() {
- return "Microsoft";
- }
- };
- // 抽象方法:擷取生産廠商
- abstract String getProducer();
- private String value;
- OSType(String value){
- this.value = value;
- }
- public String getValue() {
- return value;
- }
- public void setValue(String value) {
- this.value = value;
- }
- }
通過javac編譯後産生了一個OSType.class檔案和兩個内部類OSType$1.class、OSType$2.class檔案,通過javap指令反編譯後:
Java代碼
- javap OSType
- Compiled from "OSType.java"
- public abstract class OSType extends java.lang.Enum<OSType> {
- public static final OSType Linux;
- public static final OSType Windows;
- public static OSType[] values();
- public static OSType valueOf(java.lang.String);
- abstract java.lang.String getProducer();
- public java.lang.String getValue();
- public void setValue(java.lang.String);
- OSType(java.lang.String, int, java.lang.String, OSType$1);
- static {};
- }
- javap OSType$1
- Compiled from "OSType.java"
- final class OSType$1 extends OSType {
- OSType$1(java.lang.String, int, java.lang.String);
- java.lang.String getProducer();
- }
- javap OSType$2
- Compiled from "OSType.java"
- final class OSType$2 extends OSType {
- OSType$2(java.lang.String, int, java.lang.String);
- java.lang.String getProducer();
- }
我們發現OSType 被編譯成了abstract 類,兩個子類則被編譯成了final 類。這樣就可以很容易的了解為什麼在枚舉中定義一個抽象方法必須要在枚舉成員中實作了。
當然你也可以在枚舉屬性中直接定義方法,然而這種方法無法被外部調用,是以也就沒什麼意義。
5.枚舉與switch
我敢說很多人之是以使用枚舉就是因為枚舉可以被switch 無縫支援,可以像switch基本資料類型一樣高效,然而新版本的JDK中已經支援switch(String) 這種方式,那麼枚舉還具備它天生的優勢嗎?
首先,定義一個常量類,用于表示不同的作業系統類型:
Java代碼
- public class OSTypeConstatns {
- public static String LINUX = "linux";
- public static String WINDOWS = "windows";
- }
然後,分别調用OSType 枚舉與OSTypeConstatns 常量類進行switch 操作:
Java代碼
- OSType ost = OSType.Windows;
- switch (ost) {
- case Linux:
- break;
- case Windows:
- break;
- default:
- break;
- }
- String ostc = OSTypeConstatns.WINDOWS;
- switch (ostc) {
- case "windows":
- break;
- case "linux":
- break;
- default:
- break;
- }
兩種方式都可以達到相同的目的:根據指定的值,進行指定的操作。然而事實卻并非如此,兩者的效率卻相差很大,通過javap 反編譯得到位元組碼對比:
Java代碼
- Code:
- stack=2, locals=4, args_size=1
- 0: getstatic #2 // Field OSTypeConstatns.WINDOWS:L
- java/lang/String;
- 3: astore_1
- 4: aload_1
- 5: astore_2
- 6: iconst_m1
- 7: istore_3
- 8: aload_2
- 9: invokevirtual #3 // Method java/lang/String.hashCod
- e:()I
- //使用Stirng類型進行switch
- 12: lookupswitch { // 2
- 102977780: 54
- 1349493379: 40
- default: 65
- }
- 40: aload_2
- 41: ldc #4 // String windows
- 43: invokevirtual #5 // Method java/lang/String.equals:
- (Ljava/lang/Object;)Z
- 46: ifeq 65
- 49: iconst_0
- 50: istore_3
- 51: goto 65
- 54: aload_2
- 55: ldc #6 // String linux
- 57: invokevirtual #5 // Method java/lang/String.equals:
- (Ljava/lang/Object;)Z
- 60: ifeq 65
- 63: iconst_1
- 64: istore_3
- 65: iload_3
- //使用枚舉類型進行switch
- 66: lookupswitch { // 2
- 0: 92
- 1: 95
- default: 98
- }
- 92: goto 98
- 95: goto 98
- 98: return
- LineNumberTable:
- line 22: 0
- line 23: 4
- line 25: 92
- line 27: 95
- line 31: 98
- StackMapTable: number_of_entries = 6
- frame_type = 254
- offset_delta = 40
- locals = [ class java/lang/String, class java/lang/String, int ]
- frame_type = 13
- frame_type = 10
- frame_type = 26
- frame_type = 2
- frame_type = 249
- offset_delta = 2
可以明顯的看到枚舉中lookupswitch 操作判斷的是枚舉的ordinal(順序号),而用String 時判斷的是字元串的hashCode,在此之前還需要計算hashCode,是以明顯枚舉的效率更高。
執行完case 裡的方法後,枚舉直接return,而String完成後還進行一些其他操作,況且String 的equals 操作肯定不如直接比較int 型來的直接,最終switch使用枚舉是一個比較好的選擇,就如同使用int型一樣高效。
6.嵌套枚舉
所謂嵌套枚舉就是在一個普通class類中定義枚舉,這樣的好處就是便于管理,如:
Java代碼
- public class EnumConstants {
- enum PCType {
- PC, MAC, PAD;
- }
- static enum OSType {
- LINUX, WINDOWS;
- }
- }
需要注意的是嵌套類型的枚舉隐式靜态(static)的,但也可以顯示聲明為static,這意味着不可能定義一個局部枚舉,或者在一個内部類中定義一個枚舉。
7.EnumMap
在java.util包下有一個EnumMap 類,顧名思義,這是一個與枚舉類型鍵一起使用的專用 Map 實作。枚舉映射中所有鍵都必須來自單個枚舉類型,該枚舉類型在建立映射時顯式或隐式地指定。枚舉映射在内部表示為數組。此表示形式非常緊湊且高效。
枚舉映射根據其鍵的自然順序 來維護(該順序是聲明枚舉常量的順序)。在 collection 視圖(keySet()、entrySet() 和 values())所傳回的疊代器中反映了這一點。
由 collection 視圖傳回的疊代器是弱一緻 的:它們不會抛出 ConcurrentModificationException,也不一定顯示在疊代進行時發生的任何映射修改的效果。
不允許使用 null 鍵。試圖插入 null 鍵将抛出 NullPointerException。但是,試圖測試是否出現 null 鍵或移除 null 鍵将不會抛出異常。允許使用 null 值。
像大多數 collection 一樣,EnumMap 是不同步的。如果多個線程同時通路一個枚舉映射,并且至少有一個線程修改該映射,則此枚舉映射在外部應該是同步的。這一般通過對自然封裝該枚舉映射的某個對象進行同步來完成。如果不存在這樣的對象,則應該使用 Collections.synchronizedMap(java.util.Map ) 方法來“包裝”該枚舉。最好在建立時完成這一操作,以防止意外的非同步通路:
Java代碼
- Map<EnumKey, V> m = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));
注意事項:所有基本操作都在固定時間内執行。雖然并不保證,但它們很可能比其 HashMap 副本更快。
EnumMap 原理并不複雜,其實它就是把key換成了枚舉值,内部用一個對象數組Object[] vals 來實作value 的存儲,而且put 與get 時比較的是枚舉值的ordinal(序号),是以效率比較高,唯一有限制的地方就是key 的取值必須是已定義的枚舉範圍内,以下是一些關鍵方法的源碼:
Java代碼
- private final Class<K> keyType;
- private transient K[] keyUniverse;
- private transient Object[] vals;
- public V put(K key, V value) {
- typeCheck(key);
- //這裡的index直接取的是枚舉的序号值
- int index = key.ordinal();
- Object oldValue = vals[index];
- vals[index] = maskNull(value);
- if (oldValue == null)
- size++;
- return unmaskNull(oldValue);
- }
- public V get(Object key) {
- return (isValidKey(key) ?
- unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);
- }
如果key的取值範圍固定且value不為空的情況下,我們就應該優先考慮使用EnumMap ,畢竟它的效率比HashMap 要穩定平均,不會出現抖動。EnumMap 特别适合擴充卡模式的使用。
EnumMap的使用:
Java代碼
- Map<OSType, Object> map = new EnumMap<OSType, Object>(OSType.class);
- map.put(OSType.Windows, "Microsoft");
- Set<OSType> keys = map.keySet();
- for (OSType key : keys) {
- System.out.println(map.get(key));
- }
- //列印結果:
- Microsoft
通過debug 我們可以看到,EnumMap 初始化時已經将OSType 的所有枚舉類型擷取并賦予keyUniverse :
8.EnumSet
與EnumMap 一樣EnumSet 同樣是Java Collections Framework 的成員,他們都在java.util包之下,EnumSet是使用枚舉類型的專用 Set 實作。枚舉 set 中所有鍵都必須來自單個枚舉類型,該枚舉類型在建立 set 時顯式或隐式地指定。枚舉 set 在内部表示為位向量。此表示形式非常緊湊且高效。此類的空間和時間性能應該很好,足以用作傳統上基于 int 的“位标志”的替換形式,具有高品質、類型安全的優勢。如果其參數也是一個枚舉 set,則批量操作(如 containsAll 和 retainAll)也應運作得非常快。
由 iterator 方法傳回的疊代器按其自然順序 周遊這些元素(該順序是聲明枚舉常量的順序)。傳回的疊代器是弱一緻的:它從不抛出 ConcurrentModificationException,也不一定顯示在疊代進行時發生的任何 set 修改的效果。
不允許使用 null 元素。試圖插入 null 元素将抛出 NullPointerException。但是,試圖測試是否出現 null 元素或移除 null 元素将不會抛出異常。
像大多數 collection 實作一樣,EnumSet 是不同步的。如果多個線程同時通路一個枚舉 set,并且至少有一個線程修改該 set,則此枚舉 set 在外部應該是同步的。這通常是通過對自然封裝該枚舉 set 的對象執行同步操作來完成的。如果不存在這樣的對象,則應該使用 Collections.synchronizedSet(java.util.Set ) 方法來“包裝”該 set。最好在建立時完成這一操作,以防止意外的非同步通路:
Java代碼
- Set<MyEnum> s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));
與EnumMap 一樣,EnumSet 的所有基本操作都在固定時間内執行。依然不能保證,但很可能比 HashSet效率更高。如果其參數也是一個枚舉 set ,則批量操作會在固定時間内執行。
與EnumMap使用方法類似,這裡我就不舉例了。
9.使用枚舉時的一些注意或技巧
1)枚舉類型不能聲明為抽象,這樣做會導緻編譯時錯誤:
Java代碼
- public abstract enum EnumName {
- }
2)枚舉同樣不能被聲明為final,這樣做會導緻編譯時錯誤。如下做法會導緻無法編譯:
Java代碼
- public final enum EnumName {
- }
3)枚舉可以不聲明為public ,如:
Java代碼
- enum EnumName {
- }
4)可以添加普通方法、靜态方法、抽象方法、構造方法,但抽象方法在枚舉類型值中必須實作。
5)枚舉可以實作接口,例如:
Java代碼
- public enum EnumName implements Serializable {
- }
6)枚舉不可以繼承,原因我就不解釋了,看到這裡的人已經很清楚了。
7)一般情況下降枚舉值定義為大寫的,當然并不是必須。
8)枚舉無法使用new 進行示例化。
9)java.lang.Enum 中的final 克隆方法確定枚舉常量永遠不能被克隆,并且序列化機制的特殊處理確定了重複執行個體不會因為反序列化而建立。
10)枚舉可以被注解修飾。
11)因為每個枚舉隻有一個執行個體,是以我們可以直接使用“==”來代替equals 比較兩個枚舉是否相同,而且這麼做效率更高。
12)枚舉的構造函數無法被聲明為public 與protected 的,這樣做會導緻編譯時錯誤,但是我們可以将構造函數顯示聲明為private,因為枚舉的構造函數隐式預設為private。
13)一個枚舉永遠不會被finalized。
14)枚舉就是枚舉,不要為枚舉添加過多的業務判斷,雖然你可以在枚舉類中這麼做,單一職責一定是最高效的。