天天看點

Java Enum 詳解

   Enum(“枚舉”)全稱為 enumeration, 是 JDK 1.5  中引入的新特性,在Java開發者眼中枚舉已經是再普通不過的一種類型了,可以說任何Java程式員都編寫過自己的枚舉類,一般這些枚舉類用來表示同一類屬性,如: 

Java代碼  

Java Enum 詳解
  1. public enum OSType {  
  2.           Linux, MacOSX, Windows, iOS, Android, DOS;  
  3. }  

        以上是一個表示作業系統類型的枚舉,在使用時我們隻需要選擇其中某種類型即可,如:OSType.Windows,這樣既簡單又友善。

        枚舉的取值僅限于已定義的所有類型,以OSType為例,也就是說我們隻能選擇OSType中定義的這些作業系統類型,即使出現新的類型我們也無法使用,除非更新OSType。這種特性一方面可以了解為局限性,另一方面也可了解為限制性,限制使用者的目标選擇。

        雖然枚舉我們經常會用到,但是,往往我們越是熟悉,越是覺得普通,平時根本不注意的事物,就越是不簡單。是以今天我們就一步步的去了解這個經常使用卻并不熟悉的“枚舉”。

        1.枚舉的父類

        定義一個新枚舉我們隻需要聲明“enum”關鍵字即可,雖說在開發過程中枚舉是一種類型,但最終“枚舉”會被編譯成一個class,通過javap 反編譯指令就可以證明這一點,在此之前我們來看一下javap 指令的使用說明:

Java代碼  

Java Enum 詳解
  1. javap -help  
  2. 用法: javap <options> <classes>  
  3. 其中, 可能的選項包括:  
  4.   -help  --help  -?        輸出此用法消息  
  5.   -version                 版本資訊  
  6.   -v  -verbose             輸出附加資訊  
  7.   -l                       輸出行号和本地變量表  
  8.   -public                  僅顯示公共類和成員  
  9.   -protected               顯示受保護的/公共類和成員  
  10.   -package                 顯示程式包/受保護的/公共類和成員 (預設)  
  11.   -p  -private             顯示所有類和成員  
  12.   -c                       對代碼進行反彙編  
  13.   -s                       輸出内部類型簽名  
  14.   -sysinfo                 顯示正在處理的類的系統資訊 (路徑, 大小, 日期, MD5 散列)  
  15.   -constants               顯示最終常量  
  16.   -classpath <path>        指定查找使用者類檔案的位置  
  17.   -cp <path>               指定查找使用者類檔案的位置  
  18.   -bootclasspath <path>    覆寫引導類檔案的位置  

        于是,我們使用javap反編譯上面的OSType枚舉,得到以下結果:

Java代碼  

Java Enum 詳解
  1. javap OSType.class  
  2. Compiled from "OSType.java"  
  3. public final class OSType extends java.lang.Enum<OSType> {  
  4.   public static final OSType Linux;  
  5.   public static final OSType MacOSX;  
  6.   public static final OSType Windows;  
  7.   public static final OSType iOS;  
  8.   public static final OSType Android;  
  9.   public static final OSType DOS;  
  10.   public static OSType[] values();  
  11.   public static OSType valueOf(java.lang.String);  
  12.   static {};  
  13. }  

        通過控制台輸出的反編譯結果可以看到,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代碼  

Java Enum 詳解
  1. package java.lang;  
  2. import java.io.Serializable;  
  3. import java.io.IOException;  
  4. import java.io.InvalidObjectException;  
  5. import java.io.ObjectInputStream;  
  6. import java.io.ObjectStreamException;  
  7. public abstract class Enum<E extends Enum<E>>  
  8.         implements Comparable<E>, Serializable {  
  9.     private final String name;  
  10.     public final String name() {  
  11.         return name;  
  12.     }  
  13.     private final int ordinal;  
  14.     public final int ordinal() {  
  15.         return ordinal;  
  16.     }  
  17.     protected Enum(String name, int ordinal) {  
  18.         this.name = name;  
  19.         this.ordinal = ordinal;  
  20.     }  
  21.     public String toString() {  
  22.         return name;  
  23.     }  
  24.     public final boolean equals(Object other) {  
  25.         return this==other;  
  26.     }  
  27.     public final int hashCode() {  
  28.         return super.hashCode();  
  29.     }  
  30.     protected final Object clone() throws CloneNotSupportedException {  
  31.         throw new CloneNotSupportedException();  
  32.     }  
  33.     public final int compareTo(E o) {  
  34.         Enum<?> other = (Enum<?>)o;  
  35.         Enum<E> self = this;  
  36.         if (self.getClass() != other.getClass() && // optimization  
  37.             self.getDeclaringClass() != other.getDeclaringClass())  
  38.             throw new ClassCastException();  
  39.         return self.ordinal - other.ordinal;  
  40.     }  
  41.     @SuppressWarnings("unchecked")  
  42.     public final Class<E> getDeclaringClass() {  
  43.         Class<?> clazz = getClass();  
  44.         Class<?> zuper = clazz.getSuperclass();  
  45.         return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;  
  46.     }  
  47.     public static <T extends Enum<T>> T valueOf(Class<T> enumType,  
  48.                                                 String name) {  
  49.         T result = enumType.enumConstantDirectory().get(name);  
  50.         if (result != null)  
  51.             return result;  
  52.         if (name == null)  
  53.             throw new NullPointerException("Name is null");  
  54.         throw new IllegalArgumentException(  
  55.             "No enum constant " + enumType.getCanonicalName() + "." + name);  
  56.     }  
  57.     protected final void finalize() { }  
  58.     private void readObject(ObjectInputStream in) throws IOException,  
  59.         ClassNotFoundException {  
  60.         throw new InvalidObjectException("can't deserialize enum");  
  61.     }  
  62.     private void readObjectNoData() throws ObjectStreamException {  
  63.         throw new InvalidObjectException("can't deserialize enum");  
  64.     }  
  65. }  

        Enum 類中有兩個成員變量:name 與ordinal,分别用于表示該枚舉的名稱和排序位置。Enum 中還有一些常用的方法,作用及原理都比較簡單,我這裡就不詳細講解了。

        2.枚舉中隐含的方法

        編譯後的自定義枚舉類是java.lang.Enum 這個抽象類的子類,并且添加了valueOf(java.lang.String)和values()的方法。然而這兩個方法卻并不是源代碼中已經定義的,甚至你根本都找不到它是在哪裡實作的。

        既然根本找不到實作,那麼我們就自己去重寫一下,于是在OSType枚舉類中試着重寫valueOf(String name)這個方法:

Java代碼  

Java Enum 詳解
  1. //The enum OSType already defines the method valueOf(String) implicitly  
  2. public OSType valueOf(String name) {  
  3. }  

        重寫valueOf(String name)方法後卻驚奇的發現,編譯器給出了錯誤提示,意思是說:該枚舉(OSType)已經隐式定義了valueOf(String)方法,我們無法進行重寫,否則編譯器會給出錯誤提示。

        原來在枚舉中已經定義了幾種方法,如:values()、valueOf(String),當重新定義它們時,編譯器會主動給出錯誤提示:

Java代碼  

Java Enum 詳解
  1. public static E[] values();  
  2. 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代碼  

Java Enum 詳解
  1. protected Enum(String name, int ordinal) {  
  2.     this.name = name;  
  3.     this.ordinal = ordinal;  
  4. }  

        雖然Enum 類是枚舉的實際父類,但這也隻是在編譯後,在編譯前我們是無法使用Enum 的任何方法和構造參數的。

        但是我們可以為自己定義的枚舉建立适合的構造參數,如:

Java代碼  

Java Enum 詳解
  1. public enum OSType {  
  2.                     Linux, MacOSX, Windows("windows"), iOS("ios", 3), Android(4), DOS();  
  3.     private int    order;  
  4.     private String value;  
  5.     OSType(){  
  6.     }  
  7.     OSType(String value){  
  8.         this.value = value;  
  9.     }  
  10.     OSType(int order){  
  11.         this.order = order;  
  12.     }  
  13.     OSType(String value, int order){  
  14.         this.order = order;  
  15.         this.value = value;  
  16.     }  
  17.     public String getValue() {  
  18.         return value;  
  19.     }  
  20.     public void setValue(String value) {  
  21.         this.value = value;  
  22.     }  
  23.     public int getOrder() {  
  24.         return order;  
  25.     }  
  26.     public void setOrder(int order) {  
  27.         this.order = order;  
  28.     }  
  29. }  

        自定義構造函數的好處就在于,我們可以為枚舉提供更豐富的“值/意義”。但是枚舉屬性的定義必須符合構造函數,這是為什麼呢?其實原理很簡單,在使用枚舉前,枚舉通過invokestatic 位元組碼關鍵字已經把所有枚舉屬性進行了初始化,初始化當然需要調用構造函數,是以枚舉屬性的定義就需要符合構造函數的定義規則。

        通過javap -c 指令,輸出更多内容:

Java代碼  

Java Enum 詳解
  1. javap -c OSType.class  
  2. Compiled from "OSType.java"  
  3. public final class OSType extends java.lang.Enum<OSType> {  
  4.   public static final OSType Linux;  
  5.   public static final OSType MacOSX;  
  6.   public static final OSType Windows;  
  7.   public static final OSType iOS;  
  8.   public static final OSType Android;  
  9.   public static final OSType DOS;  
  10.   public static OSType[] values();  
  11.     Code:  
  12.        0: getstatic     #1                  // Field $VALUES:[LOSType;  
  13.        3: invokevirtual #2                  // Method "[LOSType;".clone:()Ljava/  
  14. lang/Object;  
  15.        6: checkcast     #3                  // class "[LOSType;"  
  16.        9: areturn  
  17.   public static OSType valueOf(java.lang.String);  
  18.     Code:  
  19.        0: ldc           #4                  // class OSType  
  20.        2: aload_0  
  21.        3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Lj  
  22. ava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;  
  23.        6: checkcast     #4                  // class OSType  
  24.        9: areturn  
  25. ...  
  26.   static {};  
  27.     Code:  
  28.        0: new           #4                  // class OSType  
  29.        3: dup  
  30.        4: ldc           #9                  // String Linux  
  31.        6: iconst_0  
  32.        7: invokespecial #10                 // Method "<init>":(Ljava/lang/Strin  
  33. g;I)V  
  34.       10: putstatic     #11                 // Field Linux:LOSType;  
  35.       13: new           #4                  // class OSType  
  36.       16: dup  
  37.       17: ldc           #12                 // String MacOSX  
  38.       19: iconst_1  
  39.       20: invokespecial #10                 // Method "<init>":(Ljava/lang/Strin  
  40. g;I)V  
  41.       23: putstatic     #13                 // Field MacOSX:LOSType;  
  42.       26: new           #4                  // class OSType  
  43.       29: dup  
  44.       30: ldc           #14                 // String Windows  
  45.       32: iconst_2  
  46.       33: ldc           #15                 // String windows  
  47.       35: invokespecial #16                 // Method "<init>":(Ljava/lang/Strin  
  48. g;ILjava/lang/String;)V  
  49.       38: putstatic     #17                 // Field Windows:LOSType;  
  50.       41: new           #4                  // class OSType  
  51.       44: dup  
  52.       45: ldc           #18                 // String iOS  
  53.       47: iconst_3  
  54.       48: ldc           #19                 // String ios  
  55.       50: iconst_3  
  56.       51: invokespecial #20                 // Method "<init>":(Ljava/lang/Strin  
  57. g;ILjava/lang/String;I)V  
  58.       54: putstatic     #21                 // Field iOS:LOSType;  
  59.       57: new           #4                  // class OSType  
  60.       60: dup  
  61.       61: ldc           #22                 // String Android  
  62.       63: iconst_4  
  63.       64: iconst_4  
  64.       65: invokespecial #23                 // Method "<init>":(Ljava/lang/Strin  
  65. g;II)V  
  66.       68: putstatic     #24                 // Field Android:LOSType;  
  67.       71: new           #4                  // class OSType  
  68.       74: dup  
  69.       75: ldc           #25                 // String DOS  
  70.       77: iconst_5  
  71.       78: invokespecial #10                 // Method "<init>":(Ljava/lang/Strin  
  72. g;I)V  
  73.       81: putstatic     #26                 // Field DOS:LOSType;  
  74.       84: bipush        6  
  75.       86: anewarray     #4                  // class OSType  
  76.       89: dup  
  77.       90: iconst_0  
  78.       91: getstatic     #11                 // Field Linux:LOSType;  
  79.       94: aastore  
  80.       95: dup  
  81.       96: iconst_1  
  82.       97: getstatic     #13                 // Field MacOSX:LOSType;  
  83.      100: aastore  
  84.      101: dup  
  85.      102: iconst_2  
  86.      103: getstatic     #17                 // Field Windows:LOSType;  
  87.      106: aastore  
  88.      107: dup  
  89.      108: iconst_3  
  90.      109: getstatic     #21                 // Field iOS:LOSType;  
  91.      112: aastore  
  92.      113: dup  
  93.      114: iconst_4  
  94.      115: getstatic     #24                 // Field Android:LOSType;  
  95.      118: aastore  
  96.      119: dup  
  97.      120: iconst_5  
  98.      121: getstatic     #26                 // Field DOS:LOSType;  
  99.      124: aastore  
  100.      125: putstatic     #1                  // Field $VALUES:[LOSType;  
  101.      128: return  
  102. }  

        其中invokespecial 關鍵字的作用就是調用指定構造函數進行初始化,跟Java代碼中的new Object() 形式類似。

        4.枚舉中聲明方法

        在定義的枚舉類中我們可以聲明方法(為了友善及節省篇幅,我把代碼整理了一下),如:

Java代碼  

Java Enum 詳解
  1. public enum OSType {  
  2.                     Linux("linux"), Windows("windows");  
  3.     // 聲明的方法  
  4.     public String getOSTypeName() {  
  5.         return value;  
  6.     }  
  7.     private String value;  
  8.     // 聲明的方法  
  9.     public static void main(String args[]) {  
  10.         System.out.println(OSType.Linux.getOSTypeName());  
  11.         System.out.println(OSType.Windows.getValue());  
  12.     }  
  13.     OSType(){  
  14.     }  
  15.     OSType(String value){  
  16.         this.value = value;  
  17.     }  
  18.     // 聲明的方法  
  19.     public String getValue() {  
  20.         return value;  
  21.     }  
  22.     // 聲明的方法  
  23.     public void setValue(String value) {  
  24.         this.value = value;  
  25.     }  
  26. }  
  27. //列印結果:  
  28. linux  
  29. windows  

        從列印結果可以看到我們調用枚舉中定義的public 方法成功了。需要注意的是,如果将方法定義成private我們将無法在外部對其進行調用。

        在枚舉中我們亦可以定義抽象的方法體,但是定義了抽象方法後,必須在枚舉成員體中實作它,因為每一個枚舉成員你都可以了解成一個命名不同的類執行個體如:

Java代碼  

Java Enum 詳解
  1. public enum OSType {  
  2.                     Linux("linux") {  
  3.                         @Override  
  4.                         String getProducer() {  
  5.                             return "Red Hat";  
  6.                         }  
  7.                     },  
  8.                     Windows("windows") {  
  9.                         @Override  
  10.                         String getProducer() {  
  11.                             return "Microsoft";  
  12.                         }  
  13.                     };  
  14.     // 抽象方法:擷取生産廠商  
  15.     abstract String getProducer();  
  16.     private String value;  
  17.     OSType(String value){  
  18.         this.value = value;  
  19.     }  
  20.     public String getValue() {  
  21.         return value;  
  22.     }  
  23.     public void setValue(String value) {  
  24.         this.value = value;  
  25.     }  
  26. }  

        通過javac編譯後産生了一個OSType.class檔案和兩個内部類OSType$1.class、OSType$2.class檔案,通過javap指令反編譯後:

Java代碼  

Java Enum 詳解
  1. javap  OSType  
  2. Compiled from "OSType.java"  
  3. public abstract class OSType extends java.lang.Enum<OSType> {  
  4.   public static final OSType Linux;  
  5.   public static final OSType Windows;  
  6.   public static OSType[] values();  
  7.   public static OSType valueOf(java.lang.String);  
  8.   abstract java.lang.String getProducer();  
  9.   public java.lang.String getValue();  
  10.   public void setValue(java.lang.String);  
  11.   OSType(java.lang.String, int, java.lang.String, OSType$1);  
  12.   static {};  
  13. }  
  14. javap  OSType$1  
  15. Compiled from "OSType.java"  
  16. final class OSType$1 extends OSType {  
  17.   OSType$1(java.lang.String, int, java.lang.String);  
  18.   java.lang.String getProducer();  
  19. }  
  20. javap  OSType$2  
  21. Compiled from "OSType.java"  
  22. final class OSType$2 extends OSType {  
  23.   OSType$2(java.lang.String, int, java.lang.String);  
  24.   java.lang.String getProducer();  
  25. }  

        我們發現OSType 被編譯成了abstract 類,兩個子類則被編譯成了final 類。這樣就可以很容易的了解為什麼在枚舉中定義一個抽象方法必須要在枚舉成員中實作了。

        當然你也可以在枚舉屬性中直接定義方法,然而這種方法無法被外部調用,是以也就沒什麼意義。

        5.枚舉與switch

        我敢說很多人之是以使用枚舉就是因為枚舉可以被switch 無縫支援,可以像switch基本資料類型一樣高效,然而新版本的JDK中已經支援switch(String) 這種方式,那麼枚舉還具備它天生的優勢嗎?

        首先,定義一個常量類,用于表示不同的作業系統類型:

Java代碼  

Java Enum 詳解
  1. public class OSTypeConstatns {  
  2.     public static String LINUX   = "linux";  
  3.     public static String WINDOWS = "windows";  
  4. }  

        然後,分别調用OSType 枚舉與OSTypeConstatns 常量類進行switch 操作:

Java代碼  

Java Enum 詳解
  1. OSType ost = OSType.Windows;  
  2. switch (ost) {  
  3.     case Linux:  
  4.         break;  
  5.     case Windows:  
  6.         break;  
  7.     default:  
  8.         break;  
  9. }  
  10. String ostc = OSTypeConstatns.WINDOWS;  
  11. switch (ostc) {  
  12.     case "windows":  
  13.         break;  
  14.     case "linux":  
  15.         break;  
  16.     default:  
  17.         break;  
  18. }  

        兩種方式都可以達到相同的目的:根據指定的值,進行指定的操作。然而事實卻并非如此,兩者的效率卻相差很大,通過javap 反編譯得到位元組碼對比:

Java代碼  

Java Enum 詳解
  1. Code:  
  2. stack=2, locals=4, args_size=1  
  3.  0: getstatic     #2                  // Field OSTypeConstatns.WINDOWS:L  
  4. java/lang/String;  
  5.  3: astore_1  
  6.  4: aload_1  
  7.  5: astore_2  
  8.  6: iconst_m1  
  9.  7: istore_3  
  10.  8: aload_2  
  11.  9: invokevirtual #3                  // Method java/lang/String.hashCod  
  12. e:()I  
  13. //使用Stirng類型進行switch  
  14. 12: lookupswitch  { // 2  
  15.        102977780: 54  
  16.       1349493379: 40  
  17.      default: 65  
  18.     }  
  19. 40: aload_2  
  20. 41: ldc           #4                  // String windows  
  21. 43: invokevirtual #5                  // Method java/lang/String.equals:  
  22. (Ljava/lang/Object;)Z  
  23. 46: ifeq          65  
  24. 49: iconst_0  
  25. 50: istore_3  
  26. 51: goto          65  
  27. 54: aload_2  
  28. 55: ldc           #6                  // String linux  
  29. 57: invokevirtual #5                  // Method java/lang/String.equals:  
  30. (Ljava/lang/Object;)Z  
  31. 60: ifeq          65  
  32. 63: iconst_1  
  33. 64: istore_3  
  34. 65: iload_3  
  35. //使用枚舉類型進行switch  
  36. 66: lookupswitch  { // 2  
  37.            0: 92  
  38.            1: 95  
  39.      default: 98  
  40.     }  
  41. 92: goto          98  
  42. 95: goto          98  
  43. 98: return  
  44. LineNumberTable:  
  45. line 22: 0  
  46. line 23: 4  
  47. line 25: 92  
  48. line 27: 95  
  49. line 31: 98  
  50. StackMapTable: number_of_entries = 6  
  51. frame_type = 254   
  52.   offset_delta = 40  
  53.   locals = [ class java/lang/String, class java/lang/String, int ]  
  54. frame_type = 13   
  55. frame_type = 10   
  56. frame_type = 26   
  57. frame_type = 2   
  58. frame_type = 249   
  59.   offset_delta = 2  

        可以明顯的看到枚舉中lookupswitch 操作判斷的是枚舉的ordinal(順序号),而用String 時判斷的是字元串的hashCode,在此之前還需要計算hashCode,是以明顯枚舉的效率更高。

        執行完case 裡的方法後,枚舉直接return,而String完成後還進行一些其他操作,況且String 的equals 操作肯定不如直接比較int 型來的直接,最終switch使用枚舉是一個比較好的選擇,就如同使用int型一樣高效。

        6.嵌套枚舉

        所謂嵌套枚舉就是在一個普通class類中定義枚舉,這樣的好處就是便于管理,如:

Java代碼  

Java Enum 詳解
  1. public class EnumConstants {  
  2.     enum PCType {  
  3.                  PC, MAC, PAD;  
  4.     }  
  5.     static enum OSType {  
  6.                         LINUX, WINDOWS;  
  7.     }  
  8. }  

        需要注意的是嵌套類型的枚舉隐式靜态(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代碼  

Java Enum 詳解
  1. Map<EnumKey, V> m = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));  

        注意事項:所有基本操作都在固定時間内執行。雖然并不保證,但它們很可能比其 HashMap 副本更快。

        EnumMap 原理并不複雜,其實它就是把key換成了枚舉值,内部用一個對象數組Object[] vals 來實作value 的存儲,而且put 與get 時比較的是枚舉值的ordinal(序号),是以效率比較高,唯一有限制的地方就是key 的取值必須是已定義的枚舉範圍内,以下是一些關鍵方法的源碼:

Java代碼  

Java Enum 詳解
  1. private final Class<K> keyType;  
  2. private transient K[] keyUniverse;  
  3. private transient Object[] vals;  
  4. public V put(K key, V value) {  
  5.     typeCheck(key);  
  6.     //這裡的index直接取的是枚舉的序号值  
  7.     int index = key.ordinal();  
  8.     Object oldValue = vals[index];  
  9.     vals[index] = maskNull(value);  
  10.     if (oldValue == null)  
  11.         size++;  
  12.     return unmaskNull(oldValue);  
  13. }  
  14. public V get(Object key) {  
  15.     return (isValidKey(key) ?  
  16.     unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);  
  17. }  

        如果key的取值範圍固定且value不為空的情況下,我們就應該優先考慮使用EnumMap ,畢竟它的效率比HashMap 要穩定平均,不會出現抖動。EnumMap 特别适合擴充卡模式的使用。

        EnumMap的使用:

Java代碼  

Java Enum 詳解
  1. Map<OSType, Object> map = new EnumMap<OSType, Object>(OSType.class);  
  2. map.put(OSType.Windows, "Microsoft");  
  3. Set<OSType> keys = map.keySet();  
  4. for (OSType key : keys) {  
  5.     System.out.println(map.get(key));  
  6. }  
  7. //列印結果:  
  8. Microsoft  

        通過debug 我們可以看到,EnumMap 初始化時已經将OSType 的所有枚舉類型擷取并賦予keyUniverse :

Java Enum 詳解

        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代碼  

Java Enum 詳解
  1. Set<MyEnum> s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));  

        與EnumMap 一樣,EnumSet 的所有基本操作都在固定時間内執行。依然不能保證,但很可能比 HashSet效率更高。如果其參數也是一個枚舉 set ,則批量操作會在固定時間内執行。

        與EnumMap使用方法類似,這裡我就不舉例了。

        9.使用枚舉時的一些注意或技巧

        1)枚舉類型不能聲明為抽象,這樣做會導緻編譯時錯誤:

Java代碼  

Java Enum 詳解
  1. public abstract enum EnumName {  
  2. }  

        2)枚舉同樣不能被聲明為final,這樣做會導緻編譯時錯誤。如下做法會導緻無法編譯:

Java代碼  

Java Enum 詳解
  1. public final enum EnumName {  
  2. }  

        3)枚舉可以不聲明為public ,如:

Java代碼  

Java Enum 詳解
  1. enum EnumName {  
  2. }  

        4)可以添加普通方法、靜态方法、抽象方法、構造方法,但抽象方法在枚舉類型值中必須實作。

        5)枚舉可以實作接口,例如:

Java代碼  

Java Enum 詳解
  1. public enum EnumName implements Serializable {  
  2. }  

        6)枚舉不可以繼承,原因我就不解釋了,看到這裡的人已經很清楚了。

        7)一般情況下降枚舉值定義為大寫的,當然并不是必須。

        8)枚舉無法使用new 進行示例化。

        9)java.lang.Enum 中的final 克隆方法確定枚舉常量永遠不能被克隆,并且序列化機制的特殊處理確定了重複執行個體不會因為反序列化而建立。

        10)枚舉可以被注解修飾。

        11)因為每個枚舉隻有一個執行個體,是以我們可以直接使用“==”來代替equals 比較兩個枚舉是否相同,而且這麼做效率更高。

        12)枚舉的構造函數無法被聲明為public 與protected 的,這樣做會導緻編譯時錯誤,但是我們可以将構造函數顯示聲明為private,因為枚舉的構造函數隐式預設為private。

        13)一個枚舉永遠不會被finalized。

        14)枚舉就是枚舉,不要為枚舉添加過多的業務判斷,雖然你可以在枚舉類中這麼做,單一職責一定是最高效的。