19、java中枚举
枚舉是什么?
枚舉就是將一個有限集合中的所有元素列舉出來,在java中使用可以使用enum關鍵字來聲明一個枚舉類。
為什么使用枚舉?
之前當用到一些常量時,便臨時聲明一個,這樣使得代碼看起來很亂,這里一個常量,那里一個常量,所以可以想著把一些具有關聯性的常量封裝到一個類中,類中的每一個變量使用public static final來進行修飾,這樣雖然解決了問題,但是不是很優雅,而且還是有點麻煩,這個時候便可以使用枚舉,直接使用enum關鍵字聲明一個枚舉類,直接將常量在枚舉類中聲明即可,不需顯示使用任何修飾符修飾,因為這些問題編譯器幫我們做了,使用枚舉更加安全、方便、優雅。
怎么使用枚舉?
使用enum聲明枚舉類即可,我感覺有兩種使用枚舉的方式,一種是使用默認的方式,聲明的變量及是枚舉值;另一種是想用幾個字段拼成一個字段作為枚舉值,這兩種使用方式如下:
//字面值及是枚舉值 public enum EnumTest{Cancel,Waiting,Payed;}---------------------------------------------------------------------------//使用自定義的構造方法創建枚舉類,已達到多個字段拼接成一個字段作為枚舉值 public enum EnumTest{Cancel("0","000"),Waiting("1","111"),Payed("4","444");private final String name;private final String msg;//在這里自定義一個構造方法即可,注意一定要講構造方法私有化private EnumTest(String name,String msg) {this.name = name;this.msg = msg;}//重寫構造方法public String toString() {return this.name+"-"+this.msg;} }-----------------------------------------------------------------------------//使用枚舉類型 public class Test{public static void main(String[] args) {//使用時會發現多出了values方法(未自定義),這個方法可以獲取到枚舉類型中所有的枚舉值EnumTest[] values = EnumTest.values();for (int i = 0; i < values.length; i++) {System.out.println(values[i]);}//使用時會發現多出了valuesOf方法(未自定義),這個方法可以獲取指定的枚舉值EnumTest cancel = EnumTest.valueOf("Cancel");System.out.println(cancel);//直接使用枚舉值System.out.println(EnumTest.Cancel);} }枚舉類型的使用非常簡單,但是肯定會有很多疑惑,接下來根據源碼來解析一下枚舉的實現原理。
枚舉的實現原理:
在枚舉類上使用 Ctrl+T 可以看到自定義枚舉類的體系架構,它竟然是java.lang.Enum的子類,但是聲明時并沒有繼承這個Enum類,這時復制java.lang.Enum到API中查看,可以看到解釋文檔這么寫:“Enum是所有Java語言枚舉類型的公共基類。 有關枚舉的更多信息,包括由編譯器合成的隱式聲明方法的描述,請參見The Java? Language Specification的第8.9節,當使用枚舉類型作為集合的類型或映射中的鍵的類型時,可以使用專門且高效的set和map實現。”,這里邊有幾個關鍵詞:公共基類、編譯器合成的隱式聲明方法、作為集合的類型。根據這幾個詞可以推斷被enum修飾之后,編譯器會隱式的去讓當前枚舉類繼承Enum類,并且隱式的聲明一些處理的方法。所以這里先看一下Enum類的源碼分析:
/*** Enum是所有Java語言枚舉類型的公共基類,更多關于枚舉的信息,包括對編譯器合成的隱式聲明方法的描述,可以參見java語言標準文檔. * * 在將枚舉類型用作集合的類型或映射中的鍵的類型時,可以使用更高效的:java.util.EnumSet和java.util.EnumMap*/ public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {//枚舉常量的名稱, 正如枚舉聲明中聲明的那樣.應該使用toString方法而不是訪問這個字段private final String name;//返回此枚舉常量的名稱,與在其枚舉聲明中聲明的名稱完全相同。public final String name() {return name;}//枚舉常數的序號(它在枚舉聲明中的位置,其中初始常數的序號為0)。private final int ordinal;//返回此枚舉常數的序號(其在枚舉聲明中的位置,其中初始常數的序號為0)。public final int ordinal() {return ordinal;}//唯一的構造函數,外界不能調用此構造函數,它由編譯器在響應枚舉類型聲明時發出的代碼使用。protected Enum(String name, int ordinal) {this.name = name;this.ordinal = ordinal;}//返回此枚舉常量的名稱public String toString() {return name;}//如果指定的對象等于枚舉常量,則返回true。public final boolean equals(Object other) {return this==other;}//返回此枚舉常量的散列代碼public final int hashCode() {return super.hashCode();}//拋出CloneNotSupportedException。這保證了枚舉不會被克隆,保證枚舉類是單例的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() && // optimizationself.getDeclaringClass() != other.getDeclaringClass())throw new ClassCastException();return self.ordinal - other.ordinal;}//返回與枚舉常量的枚舉類型對應的類對象。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);}//枚舉類不能有finalize方法。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修飾的代碼,會被編譯成一個繼承了Enum類的最終類,所以被enum修飾的枚舉類不可被繼承也不可繼承其他類 public final class com.czp.enumeration.EnumTest extends java.lang.Enum<com.czp.enumeration.EnumTest> {//聲明的枚舉值會聲明成EnumTest類型的變量public static final com.czp.enumeration.EnumTest Cancel;public static final com.czp.enumeration.EnumTest Waiting;public static final com.czp.enumeration.EnumTest Payed;//使用一個靜態代碼塊進行聲明的枚舉值的賦值操作static {};Code://--------------------- 處理三個枚舉值 ----------------------------////創建一個EnumTest對象,并且其引用進棧0: new #1 // class com/czp/enumeration/EnumTest//復制棧頂數值,并且復制值進棧 3: dup//將Cancel作為一個String類型值放在棧頂 4: ldc #14 // String Cancel //將0壓入棧頂6: iconst_0 //使用Enum中的構造方法 Enum(String name, int ordinal) 創建一個對象,name=Cancel,ordinal=07: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V //將構造的對象賦給變量Cancel10: putstatic #19 // Field Cancel:Lcom/czp/enumeration/EnumTest; 13: new #1 // class com/czp/enumeration/EnumTest16: dup17: ldc #21 // String Waiting19: iconst_120: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V23: putstatic #22 // Field Waiting:Lcom/czp/enumeration/EnumTest;26: new #1 // class com/czp/enumeration/EnumTest29: dup30: ldc #24 // String Payed32: iconst_233: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V36: putstatic #25 // Field Payed:Lcom/czp/enumeration/EnumTest;//------------------- 創建數組,將所有枚舉值放入到數組中 ---------------------// 39: iconst_3// 相當于創建一個數組,數組引用入棧,長度為棧頂元素340: anewarray #1 // class com/czp/enumeration/EnumTest //復制棧頂數值,并且復制值進棧 43: dup//int型常量值0入棧,作為數組下標44: iconst_0//獲取到Cancel45: getstatic #19 // Field Cancel:Lcom/czp/enumeration/EnumTest; // 依次取出棧頂元素,根據類型進行數組賦值操作 48: aastore49: dup50: iconst_151: getstatic #22 // Field Waiting:Lcom/czp/enumeration/EnumTest;54: aastore55: dup56: iconst_257: getstatic #25 // Field Payed:Lcom/czp/enumeration/EnumTest;60: aastore//將構建的數組對象賦給VALUES61: putstatic #27 // Field ENUM$VALUES:[Lcom/czp/enumeration/EnumTest;64: return //返回值為空//創建一個values方法,用于獲取所有枚舉值(大致操作就是新弄一個數組,將上邊VALUES中數據復制到新數組,返回新數組)public static com.czp.enumeration.EnumTest[] values();Code://獲取到靜態域中數組VALUES0: getstatic #27 // Field ENUM$VALUES:[Lcom/czp/enumeration/EnumTest;3: dup4: astore_0 // 該指令的行為類似于astore指令index為0的情況。5: iconst_06: aload_0 // 當前frame的局部變量數組中下標為0的引用型局部變量進棧7: arraylength //棧頂的數組引用(arrayref)出棧,該數組的長度進棧。8: dup9: istore_1 //將棧頂int型數值存入第一個局部變量10: anewarray #1 // class com/czp/enumeration/EnumTest13: dup14: astore_215: iconst_016: iload_1 //第二個int型局部變量進棧//調用靜態方法17: invokestatic #35 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V20: aload_2 21: areturn //從方法中返回一個對象的引用//根據枚舉值的字面值獲取到對應的枚舉元素public static com.czp.enumeration.EnumTest valueOf(java.lang.String);Code:0: ldc #1 // class com/czp/enumeration/EnumTest2: aload_03: invokestatic #43 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;//類型轉換檢查,如果該檢查未通過將會拋出ClassCastException異常6: checkcast #1 // class com/czp/enumeration/EnumTest9: areturn可以看到枚舉類型被編譯器編譯成一個同名的java類,所以這里這可以看出枚舉實質就是類,這個類繼承Enum類,另外每一個枚舉值都會被聲明成一個當前類類型的變量,默認使用Enum中的構造方法初始化這個變量,并且編譯器還自定義添加了幾個方法用于操作枚舉類。
有的時候想利用枚舉類中的枚舉值作為鍵,然后存儲鍵值對到Map中,如何實現呢?很簡單,使用HashMap集合,泛型中鍵使用枚舉類作為類型即可,但是呢,java為了追求更好的效果,提供了EnumMap類來處理這類問題,看一下EnumMap的使用和基本原理(底層使用數組來記錄元素的添加):
public static void main(String[] args) {//使用EnumMap來處理枚舉集合的問題EnumMap<EnumTest, String> enumMap = new EnumMap<>(EnumTest.class);//正好每一個枚舉值的類型是一個枚舉類型,所以直接用即可enumMap.put(EnumTest.Cancel, "Cancel");enumMap.put(EnumTest.Waiting, "Waiting");enumMap.put(EnumTest.Cancel, "Cancel");Set<EnumTest> keySet = enumMap.keySet();for (EnumTest enumTest : keySet) {System.out.println(enumMap.get(enumTest));} }---------------------------------------------------------------------//EnumMap實現的基本原理,底層用的是一個數組來存儲元素 // Map implementation for use with enum type keys. Enum maps are represented internally as arrays. // 一個用于處理枚舉類型的Map集合,底層的實現方式是用的數組 //這里只看一下EnumMap是如何存儲鍵值對的 public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements java.io.Serializable, Cloneable {//集合鍵的枚舉類型的類對象private final Class<K> keyType;//鍵值對中所有值的數組private transient Object[] vals;//映射個數private transient int size = 0;//判斷key的類型是否正確private void typeCheck(K key) {Class<?> keyClass = key.getClass();if (keyClass != keyType && keyClass.getSuperclass() != keyType)throw new ClassCastException(keyClass + " != " + keyType);}//判斷value的值private Object maskNull(Object value) {return (value == null ? NULL : value);}//添加元素//將枚舉值對應的編號作為key,value是傳入的值public V put(K key, V value) {typeCheck(key);//獲取到當前key,也就是枚舉值對應的編號int index = key.ordinal();//將編號作為下標在vals中獲取對應的值Object oldValue = vals[index];//替換或者添加一個元素vals[index] = maskNull(value);if (oldValue == null)size++;return unmaskNull(oldValue);}}還有一個單列集合EnumSet專門用于處理枚舉類型的集合,其使用和基本原理如下(底層用一個long類型的值來標記哪個元素已經添加):
public static void main(String[] args) {EnumSet<EnumTest> enumSet = EnumSet.allOf(EnumTest.class);enumSet.add(EnumTest.Cancel);enumSet.add(EnumTest.Waiting);enumSet.add(EnumTest.Payed);for (EnumTest e : enumSet) {System.out.println(e);} }--------------------------------------------------------------public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E> implements Cloneable, java.io.Serializable {//類型final Class<E> elementType;//枚舉中所有元素final Enum<?>[] universe;//-------------------------------創建一個EnumSet實例-------------------------////創建一個空的集合public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {//可以看到實例化的是RegularEnumSet或者JumboEnumSet類的對象if (universe.length <= 64)return new RegularEnumSet<>(elementType, universe);elsereturn new JumboEnumSet<>(elementType, universe);}//使用指定的枚舉類型創建一個集合public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {EnumSet<E> result = noneOf(elementType);result.addAll();return result;}//添加元素時,調用的是子類中的方法,因為EnumSet中沒有添加的方法,代碼如下://說一下RegularEnumSet和JumboEnumSet(這部分代碼是在別處貼過來的)//---------------RegularEnumSet實現代碼---------------------//class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {//僅僅使用這個值來記錄集合中的元素,long類型占8個字節,下邊只用最低8位計算private long elements = 0L;RegularEnumSet(Class<E>elementType, Enum<?>[] universe) {super(elementType, universe);}//添加方法public boolean add(E e) {typeCheck(e);long oldElements = elements;//假設原本集合為空,則elements為0(0000 0000),這是添加編號為1的枚舉值,計算如下:// 先將 1 左移1位 ,則變成// 0000 0001 ----》 0000 0010// 然后與之前的elements做與運算// 0000 0000// 0000 0010 |//----------------// 0000 0010//這個地方設計的比較巧妙,因為long型一共占用64位,所以這里就用每一位表示當前集合中是否包含對應的枚舉值//例如:0000 0010,低的第二位為1,此時枚舉中的第一個枚舉值添加進來了,//依次類推;當第三位上是1時,則表示第二個枚舉值添加進來了//而最多只能到64位,這也是為什么只有小于或等于64才創建RegularEnumSet對象elements |= (1L << ((Enum<?>)e).ordinal());return elements != oldElements;}}//---------------umboEnumSet實現代碼---------------------//class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {//這個集合的位向量表示。這個數組的第j個元素的第i位表示這個集合中存在宇宙[64*j +i]。private long elements[];JumboEnumSet(Class<E>elementType, Enum<?>[] universe) {super(elementType, universe);elements = new long[(universe.length + 63) >>> 6];}//實現的基本思想:將是按著64位分組,當枚舉值的個數大于64時,就開始分,使用枚舉個數除以64看可以分幾組//然后每一組再用如RegularEnumSet中添加的方法進行記錄哪些元素已經添加了public boolean add(E e) {typeCheck(e);int eOrdinal = e.ordinal();//判斷一下要添加的枚舉值應該在第幾組int eWordNum = eOrdinal >>> 6;//獲取到當前組的long類型的值,它是64位,按著RegularEnumSet中的add方法繼續分析long oldElements = elements[eWordNum];elements[eWordNum] |= (1L << eOrdinal);boolean result = (elements[eWordNum] != oldElements);if (result)size++;return result;}}//-------------------------------創建一個EnumSet實例-------------------------// }?
總結
以上是生活随笔為你收集整理的19、java中枚举的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: win7电脑配置要求(win7 电脑配置
- 下一篇: 20、java中的类加载机制