目錄java
本篇博客是對JDK1.5的新特性枚舉的一波小小的總結,主要是昨天在看一部分面試題的時候,遇到了枚舉類型的題目,發現本身有許多細節還須要增強,作起來都模棱兩可,是時候總結一波了。程序員
很少bb,直接開門見山,我遇到這樣一道也許很簡單的題目:面試
enum AccountType { SAVING, FIXED, CURRENT; private AccountType() { System.out.println(「It is a account type」); } } class EnumOne { public static void main(String[]args) { System.out.println(AccountType.FIXED); } }
問打印的結果是啥?正確答案以下:數組
It is a account type It is a account type It is a account type FIXED
至於結果爲啥是這個,且看我慢慢總結。數據結構
存在即合理。this
我賊喜歡這句聖經,每次我一解釋不了它爲何出現的時候,就不自覺地用上這句話。.net
枚舉必定有他存在的價值,在一些時候,咱們須要定義一個類,這個類中的對象是有限且固定的,好比咱們一年有四個季節,春夏秋冬。code
在枚舉被支持以前,咱們該如何定義這個Season類呢?可能會像下面這樣:對象
public class Season { //private修飾構造器,沒法隨意建立對象 private Season(){} //final修飾提供的對象在類外不能改變 public static final Season SPRING = new Season(); public static final Season SUMMER = new Season(); public static final Season AUTUMN = new Season(); public static final Season WINTER = new Season(); }
在定義上,這個Season類能夠完成咱們的預期,它們各自表明一個實例,且不能被改變,外部也不能隨便建立實例。blog
但,經過自定義類實現枚舉的效果有個顯著的問題:代碼量很是大。
因而,JDK1.5,枚舉類應運而生。
enum
關鍵字用以定義枚舉類,這是一個和class
,interface
關鍵字地位至關的關鍵字。也就是說,枚舉類和咱們以前使用的類差不太多,且enum和class修飾的類若是同名,會出錯。
有一部分規則,類須要遵循的,枚舉類也遵循:
也有一部分規則,枚舉類顯得不同凡響:
public static final
修飾。枚舉類.枚舉常量
的方式調用。知道這些以後,咱們能夠用enum關鍵字從新定義枚舉類:
public enum Season{ //定義四個實例 SPRING,SUMMER,AUTUMN,WINTER; }
須要注意的是,在JDK1.5枚舉類加入以後,switch-case語句進行了擴展,其控制表達式能夠是任意枚舉類型,且能夠直接使用枚舉值的名稱,無需添加枚舉類做爲限定。
Enum類是全部enum關鍵字修飾的枚舉類的頂級父類,裏頭定義的方法默認狀況下,是通用的,咱們來瞅它一瞅:
public abstract class Enum<E extends Enum<E>> extends Object implements Comparable<E>, Serializable
咱們能夠發現,Enum實際上是一個繼承自Object類的抽象類(Object類果真是頂級父類,不可撼動),並實現了兩個接口:
protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; }
官方文檔這樣說的:程序員不能去調用這個構造器,它用於編譯器響應enum類型聲明發出的代碼,關於這一點,咱們後面體會會更加深入一些。
關於Object類中的方法,這邊就不贅述了,主要提一提特殊的方法。
public final String name()
返回這個枚舉常量的名稱。官方建議:大多數狀況,最好使用toString()方法,由於能夠返回一個友好的名字。而name()方法以final修飾,沒法被重寫。
public String toString()
源碼上看,toString()方法和name()方法是相同的,可是建議:若是有更友好的常量名稱顯示,能夠重寫toString()方法。
public final int ordinal()
返回此枚舉常量的序號(其在enum聲明中的位置,其中初始常量的序號爲零)。
大多數程序員將不須要這種方法。它被用於複雜的基於枚舉的數據結構中,如EnumSet和EnumMap。
public final int compareTo(E o)
這個方法用於指定枚舉對象比較順序,同一個枚舉實例只能與相同類型的枚舉實例進行比較。
public final int compareTo(E o) { Enum<?> other = (Enum<?>)o; Enum<E> self = this; if (self.getClass() != other.getClass() && // optimization //getDeclaringClass()方法返回該枚舉常量對應Enum類的類對象 self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); //該枚舉常量順序在o常量以前,返回負整數 return self.ordinal - other.ordinal; }
public static <T extends Enum
> T valueOf(Class enumType,
String name)
該靜態方法返回指定枚舉類中指定名稱的枚舉常量。
爲何會想到總結這個方法呢?其實也是有必定心路歷程的,官方文檔特別強調了一句話:
Note that when using an enumeration type as the type of a set or as the type of the keys in a map, specialized and efficient set and map implementations are available.
通常Note開頭的玩意兒,仍是比較重要的。大體意思以下:
當使用枚舉類型做爲集合的類型或映射中的鍵的類型時,可使用專門化且有效的集合和映射實現。
看完很是不理解,因而開始查找資料,發現有一種用法:
Arrays.asList(AccountType.values())
很明顯調用了這個枚舉類的values()方法,可是剛纔對枚舉類的方法一通分析,也沒看到有values()方法啊。可是編譯器確實提示,有,確實有!
這是怎麼回事呢?JDK文檔是這麼說的:
The compiler automatically adds some special methods when it creates an enum. For example, they have a static values method that returns an array containing all of the values of the enum in the order they are declared.
編譯器會在建立一個枚舉類的時候,自動在裏面加入一些特殊的方法,例如靜態的values()方法,它將返回一個數組,按照枚舉常量聲明的順序存放它們。
這樣一來,枚舉類就能夠和集合等玩意兒很好地配合在一塊兒了,具體咋配合,之後遇到了就知道了。
關於這一點,待會反編譯以後會更加印象深入。
注:因爲學識尚淺,這部份內容總結起來虛虛的,可是總歸查找了許多的資料,若有說的不對的地方,還望評論區批評指正。
那麼,回到咱們文章開頭提到的那到面試題,咱們根據結果來推測程序運行以後發生的狀況:
System.out.println(AccountType.FIXED);
將會調用toString()方法,因爲子類沒有重寫,那麼將會返回name值,也就是"FIXED"
。至此,咱們的猜想結束,其實確實也大差不差了,大體就是這個過程。在一番查閱資料以後,我又嘗試着去反編譯這個枚舉類文件:
咱們先用javap -p AccountType.class
命令試着反編譯以後查看全部類和成員。
爲了看看static中發生的狀況,我試着用更加詳細的指令,javap -c -l AccountType.class
,試圖獲取本地變量信息表和行號,雖然我大機率仍是看不太懂的。
咱們以其中一個爲例,參看虛擬機字節碼指令表,大體過程以下:
static {}; Code: 0: new #4 //建立一個對象,將其引用值壓入棧 3: dup //複製棧頂數值並將複製值壓入棧頂 4: ldc #10 //將String常量值SAVING從常量池推送至棧頂 6: iconst_0 //將int型0推送至棧頂 7: invokespecial #11 //調用超類構造器 10: putstatic #12 //爲指定的靜態域賦值
如下爲由我的理解簡化的編譯結構:
public final class AccountType extends java.lang.Enum<AccountType> { //靜態枚舉常量 public static final AccountType SAVING; public static final AccountType FIXED; public static final AccountType CURRENT; //存儲靜態枚舉常量的私有靜態域 private static final AccountType[] $VALUES; //編譯器新加入的靜態方法 public static AccountType[] values(); //調用實例方法獲取指定名稱的枚舉常量 public static AccountType valueOf(java.lang.String); static { //建立對象,傳入枚舉常量名和順序 SAVING = new AccountType("SAVING",0); FIXED = new AccountType("FIXED",1); CURRENT = new AccountType("CURRENT",2); //給靜態域賦值 $VALUES = new AccountType[]{ SAVING,FIXED,CURRENT } }; }
Enum類的構造器,在感應到enum關鍵字修飾的類以後,將會被調用,傳入枚舉常量的字符串字面量值(name)和索引(ordinal),建立的實例存在私有靜態域&VALUES
中。
並且編譯器確實會添加靜態的values()方法,用以返回存放枚舉常量的數組。
public enum EnumSingleton { INSTANCE; public EnumSingleton getInstance(){ return INSTANCE; } }
這部分等到之後總結單例模式再侃,先在文末貼個地址。