Enum類型詳解

咱們先來看看簡單的枚舉類
package com.xiaotang;
public enum AA{
	read, black, green
}
這是最簡單的一個枚舉應用,然後調用的時候我們都知道通過 AA.read來使用,那麼這麼簡單操作背後又蘊藏着什麼樣的驚人的祕密呢?接下來讓我們一步步去揭祕!
首先咱先 javap命令來一波(此處配置的參數是 -private ):

得到如下結果:

通過以上分析我們可以是不是就可以得到枚舉類型對應的類中的屬性了吧!但是final屬性要初始化啊,通過修改命令參數(在此處,我使用 -v 顯示操作屬性的指令集)得到指令集代碼:
  static {};
  Code:
   Stack=4, Locals=0, Args_size=0
   0:	new	#1; //class com/xiaotang/AA
   3:	dup
   4:	ldc	#14; //String read
   6:	iconst_0
   7:	invokespecial	#15; //Method "<init>":(Ljava/lang/String;I)V
   10:	putstatic	#19; //Field read:Lcom/xiaotang/AA;
   13:	new	#1; //class com/xiaotang/AA
   16:	dup
   17:	ldc	#21; //String black
   19:	iconst_1
   20:	invokespecial	#15; //Method "<init>":(Ljava/lang/String;I)V
   23:	putstatic	#22; //Field black:Lcom/xiaotang/AA;
   26:	new	#1; //class com/xiaotang/AA
   29:	dup
   30:	ldc	#24; //String green
   32:	iconst_2
   33:	invokespecial	#15; //Method "<init>":(Ljava/lang/String;I)V
   36:	putstatic	#25; //Field green:Lcom/xiaotang/AA;
   39:	iconst_3
   40:	anewarray	#1; //class com/xiaotang/AA
   43:	dup
   44:	iconst_0
   45:	getstatic	#19; //Field read:Lcom/xiaotang/AA;
   48:	aastore
   49:	dup
   50:	iconst_1
   51:	getstatic	#22; //Field black:Lcom/xiaotang/AA;
   54:	aastore
   55:	dup
   56:	iconst_2
   57:	getstatic	#25; //Field green:Lcom/xiaotang/AA;
   60:	aastore
   61:	putstatic	#27; //Field ENUM$VALUES:[Lcom/xiaotang/AA;
   64:	return

通過分析指令集可以得到枚舉類的屬性賦值如下:
public static final AA read = new AA("read", 0);
public static final AA black = new AA("black", 1);
public static final AA green = new AA("green", 2);
private static final AA ENUM$VALUES[] = new AA[] { read, black, green };

可以看到 枚舉類AA中有個私有構造方法 private AA(String name,int i) 但是javap命令是查看不了私有方法的指令集的(我目前沒有找到,找到的了可以告訴我下,謝謝),那麼怎麼去看構造方法裏的內容呢,這裏只能是猜測!構造方法無非就是去操作屬性,而枚舉類的屬性都是AA類型,這裏我們需要去查看它的父類Enum了。
可以看到Enum類中有如下屬性和方法:
private final String name;
private final int ordinal;
public final String name() {
	return name;
}
public final int ordinal() {
	return ordinal;
 }
protected Enum(String name, int ordinal) {
	this.name = name;
	this.ordinal = ordinal;
 }

而我們通過 測試: AA aa=AA.black; System.out.printlen(aa.name()); //輸出 black
所以我們猜測私有構造方法內容:
private AA(String name,int ordinal)
{
super(name,ordinal);
}

我們再看看 編輯器給枚舉類新增其他兩個方法:
(1)public static com.xiaotang.AA[] values()
指令集如下:
 Code:
   Stack=5, Locals=3, Args_size=0
   0:	getstatic	#27; //Field ENUM$VALUES:[Lcom/xiaotang/AA;
   3:	dup
   4:	astore_0
   5:	iconst_0
   6:	aload_0
   7:	arraylength
   8:	dup
   9:	istore_1
   10:	anewarray	#1; //class com/xiaotang/AA
   13:	dup
   14:	astore_2
   15:	iconst_0
   16:	iload_1
   17:	invokestatic	#35; //Method java/lang/System.arraycopy:  (Ljava/lang/Object;ILjava/lang/Object;II)V
   20:	aload_2
   21:	areturn
  LineNumberTable: 
   line 1: 0

通過指令集我們可以還原values方法:
public static AA[] values(){
AA[] array=ENUM$VALUES;
int length=array.length;
AA[] newArray=new AA[3];
System.arrayCopy(array,0,newArray,0,3);
return newArray;
}
(2)public static AA valueOf(java.lang.String);
指令集如下:
 Code:
   Stack=2, Locals=1, Args_size=1
   0:	ldc	#1; //class com/xiaotang/AA
   2:	aload_0
   3:	invokestatic	#43; //Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
   6:	checkcast	#1; //class com/xiaotang/AA
   9:	areturn<pre name="code" class="java"> 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 const " + enumType +"." + name);
    }

LineNumberTable: line 1: 0

分析知道,該方法就是調用了 Enum.valueOf(AA.class,string) 並作爲返回值返回,我們在看看Enum的valueOf方法:
 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 const " + enumType +"." + name);
    }
返回就是T result,我們在看看 enumType.enumConstantDirectory()幹了啥,點進去發現它返回的就是一個map集合而已,從中獲取 key值name對應的value值,那麼這個map集合是什麼內容呢?
Map<String, T> enumConstantDirectory() {
	if (enumConstantDirectory == null) {
            T[] universe = getEnumConstantsShared();
            if (universe == null)
                throw new IllegalArgumentException(
                    getName() + " is not an enum type");
            Map<String, T> m = new HashMap<String, T>(2 * universe.length);
            for (T constant : universe)
                m.put(((Enum)constant).name(), constant);
            enumConstantDirectory = m;
        }
        return enumConstantDirectory;
    }
打開getEnumConstantsShared方法,就能看到了結果:
T[] getEnumConstantsShared() {
	if (enumConstants == null) {
	    if (!isEnum()) return null;
	    try {
		final Method values = getMethod("values");
                java.security.AccessController.doPrivileged
                    (new java.security.PrivilegedAction() {
                            public Object run() {
                                values.setAccessible(true);
                                return null;
                            }
                        });
		enumConstants = (T[])values.invoke(null);
	    }
	    // These can happen when users concoct enum-like classes
	    // that don't comply with the enum spec.
	    catch (InvocationTargetException ex) { return null; }
	    catch (NoSuchMethodException ex) { return null; }
	    catch (IllegalAccessException ex) { return null; }
	}
	return enumConstants;
    }
通過反射獲取 枚舉類中values方法,並執行該方法將結果集T[]返回過去,然後遍歷存進Map集合中,枚舉類的values方法獲取的不正是AA 的 ENUM$VALUES複製出來的數組屬性嗎?它的值是new AA[] { read, black, green }; 可以看到 Map集合中存儲的key 爲 每個數組元素對應的調用name(),存儲的value是 數組元素(AA.read,AA.black).

以上就是我們分析的枚舉類了,可以看到,我們定義一個基本的枚舉類,其實編譯器就已經給我們自動賦值了枚舉類屬性的值,給了它name和ordinal,name就是它的屬性名字,ordinal則是按照0,1,2遞增順序賦值,並並新增了一個數組,數組內容則是枚舉類的屬性集合,新增了兩個方法 valueOf(String name)和 T[] values(),有了這些基礎,爲我們認識和使用枚舉類有了很大的幫助!現在就讓我們一起來使用一波枚舉類吧!

應用一: 枚舉類與switch的應用
public class Test {
	public static void main(String[] args) {
		switch (AA.black) {
		case read:
			System.out.println("read");
			break;
		case black:
			System.out.println("black");
			break;
		case green:
			System.out.println("green");
			break;
		default:
			System.out.println("other");
			break;
		}
	}
}

初一看,呀,還有這種寫法啊!switch(AA.black),不是說switch後面只能接int,char類型數據嗎?接字符串也是jdk1.7以後的事情啊,況且 AA.black是一個對象啊,更不應該啊!還有 case read, read是什麼鬼!case 接常量值好不,大家是不是有一肚子的疑惑啊!哈哈,其實我挺疑惑的,我們知道枚舉類中還有個ordinal屬性沒有用到, 下面我這樣寫,大家是不是就能理解了:
public class Test {
	public final static int READ = AA.read.ordinal();
	public final static int BLACK = AA.black.ordinal();
	public final static int GREEN = AA.green.ordinal();
	public static void main(String[] args) {
		switch (AA.read.ordinal()) {
		case READ:
			System.out.println("read");
			break;
		case BLACK:
			System.out.println("black");
			break;
		case GREEN:
			System.out.println("green");
			break;
		default:
			System.out.println("other");
			break;
		}
	}
}
但是這個代碼是不能運行的啊,編譯都通不過 case後面接的不是常量值, 它的值會根據 AA.XX.ordinal()的變化而變化,所以不能接在case 後面,這樣只是讓我們容易理解罷了!現在我們來看看,當枚舉類與switch的相遇時,編譯器給我們做了哪些事情吧!
還是先javap一波(查看方法和屬性):
Compiled from "Test.java"
public class com.xiaotang.Test extends java.lang.Object{
    private static int[] $SWITCH_TABLE$com$xiaotang$AA;
    public com.xiaotang.Test();
    public static void main(java.lang.String[]);
    static int[] $SWITCH_TABLE$com$xiaotang$AA();
}
可以看到 編譯器給我們新增了 int型的$SWITCH_TABLE$com$xiaotang$AA數組,不同的編譯器數組取名可能不一樣,還新增了$SWITCH_TABLE$com$xiaotang$AA方法,返回int型數組值。

下面我們來詳細分析一下$SWITCH_TABLE$com$xiaotang$AA方法:
static int[] $SWITCH_TABLE$com$xiaotang$AA();
  Code:
   Stack=3, Locals=1, Args_size=0
   0:	getstatic	#53; //Field $SWITCH_TABLE$com$xiaotang$AA:[I
   3:	dup
   4:	ifnull	8
   7:	areturn
   8:	pop
   9:	invokestatic	#55; //Method com/xiaotang/AA.values:()[Lcom/xiaotang/AA;
   12:	arraylength
   13:	newarray int
   15:	astore_0
   16:	aload_0
   17:	getstatic	#59; //Field com/xiaotang/AA.black:Lcom/xiaotang/AA;
   20:	invokevirtual	#27; //Method com/xiaotang/AA.ordinal:()I
   23:	iconst_2
   24:	iastore
   25:	goto	29
   28:	pop
   29:	aload_0
   30:	getstatic	#61; //Field com/xiaotang/AA.green:Lcom/xiaotang/AA;
   33:	invokevirtual	#27; //Method com/xiaotang/AA.ordinal:()I
   36:	iconst_3
   37:	iastore
   38:	goto	42
   41:	pop
   42:	aload_0
   43:	getstatic	#21; //Field com/xiaotang/AA.read:Lcom/xiaotang/AA;
   46:	invokevirtual	#27; //Method com/xiaotang/AA.ordinal:()I
   49:	iconst_1
   50:	iastore
   51:	goto	55
   54:	pop
   55:	aload_0
   56:	dup
   57:	putstatic	#53; //Field $SWITCH_TABLE$com$xiaotang$AA:[I
   60:	areturn

通過指令分析,得到執行代碼:
  public  int [] $SWITCH_TABLE$com$xiaotang$AA(){
               if($SWITCH_TABLE$com$xiaotang$AA==null)
               {
                 int[] array = new int[AA.values().length];
		 array[AA.black.ordinal()] = 2;
		array[AA.green.ordinal()] = 3;
		array[AA.read.ordinal()] = 1;
                $SWITCH_TABLE$com$xiaotang$AA=array;
              }
               return $SWITCH_TABLE$com$xiaotang$AA;
              }
咱們再來看main方法的執行內容及對應的指令集:
public static void main(String[] args) {
		switch (AA.read) {
		case read:
			System.out.println("read");
			break;
		case black:
			System.out.println("black");
			break;
		case green:
			System.out.println("green");
			break;
		default:
			System.out.println("other");
			break;
		}
對應的指令集:
Code:
   Stack=2, Locals=1, Args_size=1
   0:	invokestatic	#18; //Method $SWITCH_TABLE$com$xiaotang$AA:()[I
   3:	getstatic	#21; //Field com/xiaotang/AA.read:Lcom/xiaotang/AA;
   6:	invokevirtual	#27; //Method com/xiaotang/AA.ordinal:()I
   9:	iaload
   10:	tableswitch{ //1 to 3
		1: 36;
		2: 47;
		3: 58;
		default: 69 }
   36:	getstatic	#31; //Field java/lang/System.out:Ljava/io/PrintStream;
   39:	ldc	#37; //String read
   41:	invokevirtual	#38; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   44:	goto	77
   47:	getstatic	#31; //Field java/lang/System.out:Ljava/io/PrintStream;
   50:	ldc	#44; //String black
   52:	invokevirtual	#38; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   55:	goto	77
   58:	getstatic	#31; //Field java/lang/System.out:Ljava/io/PrintStream;
   61:	ldc	#46; //String green
   63:	invokevirtual	#38; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   66:	goto	77
   69:	getstatic	#31; //Field java/lang/System.out:Ljava/io/PrintStream;
   72:	ldc	#48; //String other
   74:	invokevirtual	#38; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   77:	return
是這樣的,在Test中定義一個int型數組,根據case先後順序:case read,case black,case green(這個順序人爲可以改變),編譯器會默認對應關係,read----->對應1, balck------->2,green------>3
然後在 新增方法中添加數組賦值, int array[]=new int[AA.values().length];
array[AA.black.ordinal()] = 2;
array[AA.green.ordinal()] = 3;
array[AA.read.ordinal()] = 1;

然後解析 switch後面對象的ordinal()值,並作爲數組array數組下標獲取對應值,在case中需要對應值。
(2)帶參數的枚舉應用
public enum AA {
	read(100), black("sdaf"), green;
	private AA(int red) {
	}
	private AA() {
	}
	private AA(String ss) {
	}
}
對應的指令集方法和屬性:
Compiled from "AA.java"
public final class com.xiaotang.AA extends java.lang.Enum{
    public static final com.xiaotang.AA read;
    public static final com.xiaotang.AA black;
    public static final com.xiaotang.AA green;
    private static final com.xiaotang.AA[] ENUM$VALUES;
    static {};
    private com.xiaotang.AA(java.lang.String, int, int);
    private com.xiaotang.AA(java.lang.String, int);
    private com.xiaotang.AA(java.lang.String, int, java.lang.String);
    public static com.xiaotang.AA[] values();
    public static com.xiaotang.AA valueOf(java.lang.String);
}












發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章