為什么要用枚舉
在博客系統中,一篇文章有且可能有這幾種狀態, 數據庫中article
文章表中state
字段存儲數值,表示其狀態:
- 0(已發表Published)
- 1(草稿Draft)
- 2(撤回撤回(Delete)
文章實體類中用整數類型的state實例變量標識狀態:
public class Article {
/* 文章狀態可能值:0,1,2 */
private int state;
...
}
Service
層調用DAO
層修改文章狀態為‘已發表’:
/**
* dao接口修改文章狀態方法
* @param articleId 文章ID
* @param state 狀態
*/
int updateState(int articleId, int state);
// `Service`層修改文章狀態的調用Dao代碼:
articleDao.updateState(id, 0);
以上代碼有兩個問題:
state
參數傳遞並沒有限定范圍(0,1,2);- 傳遞數據參數的代碼,缺少語義,不看文檔或注釋不知道0是什么含義;
先來解決第二個問題, 在JDK1.5前常用的解決方式:
/**
* 定義了文章的狀態
*/
public interface ArticleState{
// 發布狀態
int PUBLISHED = 0;
// 草稿狀態
int DRAFT = 1;
// 撤回狀態
int DELETE = 2;
}
此時修改文章狀態的代碼:
articleDao.updateState(id, ArticleState.PUBLISHED);
然而此處沒有限制必須通過ArticleState
傳遞參數,JDK1.5后提供了枚舉來解決這類問題。
Java中聲明
在java中,使用enum
關鍵字聲明枚舉類
/**
* 文章狀態枚舉類
*/
public enum ArticleStateEnum{
PUBLISHED,
DRAFT,
DELETE;
}
然后修改DAO接口:
/**
* dao接口修改文章狀態方法
* @param articleId 文章ID
* @param state 狀態
*/
int updateState(int id, ArticleStateEnum state);
接着Service調用:
// 修改文章狀態為發表
articleDao.updateState(id, ArticleStateEnum.PUBLISHED);
以上代碼語義清晰,現在傳遞參數的類型為ArticleStateEnum
, 解決了之前描述的兩個問題
枚舉的本質
使用JDK附帶工具javap
反編譯生枚舉類字節碼, 注javap反編譯只會得到public成員:
看反編譯的得到的代碼:
- class聲明,意味着枚舉的本質也是類;
- 父類聲明為
java.lang.Enum<>
, 意味着枚舉類不允許顯式使用extends聲明父類,包括聲明為java.lang.Enum<>
也會報錯; - 枚舉常量,通過
public static final
修飾符實現,ArticlestateEnum
類型聲明,意味着所有枚舉常量本質是當前枚舉類的對象; values()方法
,valueOf(String)方法
;
這些轉換工作是javac
編譯器幫我們實現的,JVM並不知道枚舉的存在,javac幫我們做了一些語法上的轉化、簡化程序員編程,這種方式稱為語法糖。
枚舉類VS普通類
枚舉類就是類,按照這個邏輯來測試下它和普通類的差別
添加構造函數:
紅色行提示編譯錯誤“找不到這樣的構造函數”,常量聲明處添加參數,如下代碼正確:
public enum ArticleStateEnum{
PUBLISHED(0, "已發布"),
DRAFT(1, "草稿"),
DELETE(2, "撤銷");
/** 代表的數值 */
private int value;
/** 信息提示 */
private String message;
ArticleStateEnum(int value, String message) {
this.value = value;
this.message = message;
}
// get方法
}
可以推測到常量聲明的地方,等價於調用構造函數,通常我們都會為字段添加GET方法不添加SET方法,保證枚舉常量的不變性。
枚舉類VS普通類的不同點:
- 不可以顯示聲明繼承關系;
- 常量聲明,等價調用構造方法;
- 允許有多個構造方法,但修飾符有且僅是
private
; - 其他地方同類一般無二,可以添加自定添加(方法、字段,抽象成員), 實現接口;
枚舉類VS匿名類
看看以下如此誇張的寫法,也能編譯成功:
觀察生成的字節碼文件:
把枚舉常量聲明的地方替換成構造方法調用new ArticleStateEnum(v1, m1)
,這不就是匿名類的聲明嗎!
現在向枚舉類內添加抽象方放,看看結果:
編譯報錯“提示有抽象方法未實現”,驗證了前面的猜想,這是匿名類的實現,不過不可以顯式的使用使用匿名實現枚舉類的方式!
常用方法
詳細參見API文檔Enum類:
public enum ArticleStateEnum{
PUBLISHED,
DRAFT,
DELETE;
public static void main(String[] args) {
ArticleStateEnum[] states = ArticleStateEnum.values(); // 1. 獲得所有枚舉常量
for(ArticleStateEnum state: states) {
System.out.println("序號:" + state.ordinal() + " 名字:" + state); //2. 輸出聲明序號和名稱
}
System.out.println("......................................");
ArticleStateEnum draft = ArticleStateEnum.valueOf("DRAFT"); //3. 獲得某個枚舉常量,依據字符串
if(ArticleStateEnum.DRAFT == draft) {
System.out.println(ArticleStateEnum.valueOf("DRAFT").name()); //4. name方法輸出名字
}
}
}
輸出...
序號:0 名字:PUBLISHED
序號:1 名字:DRAFT
序號:0 名字:DELETE
DRAFT
JAVA中枚舉的缺點
java中枚舉給我們帶來強大的語義的時候,又由於枚舉常量對象的本質,給我們帶了來龐大性,不如C語言的枚舉的輕量:
- 枚舉常量不可以像C語言一樣使用移位運算。
- 枚舉常量和外部交互麻煩,比如:
- 在mybatis中保存帶有枚舉字段的實體時,需要你編寫轉化器(除非按照默認的聲明順序);
- 轉化為JSON數據時;
- Spring MVC對請求參數封裝時,需要自定義轉換器;
- 枚舉常量無法繼承,意味着相似的枚舉類之間無法繼承,導致產生冗余代碼;
建議無特殊情況還是使用枚舉常量,畢竟軟件的正確性是最重要的