天天看點

Java 枚舉詳解

為什麼要用枚舉

在部落格系統中,一篇文章有且可能有這幾種狀态, 資料庫中

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);
           

以上代碼有兩個問題:

  1. state

    參數傳遞并沒有限定範圍(0,1,2);
  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成員:

Java 枚舉詳解

看反編譯的得到的代碼:

  1. class聲明,意味着枚舉的本質也是類;
  2. 父類聲明為

    java.lang.Enum<>

    , 意味着枚舉類不允許顯式使用extends聲明父類,包括聲明為

    java.lang.Enum<>

    也會報錯;
  3. 枚舉常量,通過

    public static final

    修飾符實作,

    ArticlestateEnum

    類型聲明,意味着所有枚舉常量本質是目前枚舉類的對象;
  4. values()方法

    valueOf(String)方法

    ;

這些轉換工作是

javac

編譯器幫我們實作的,JVM并不知道枚舉的存在,javac幫我們做了一些文法上的轉化、簡化程式員程式設計,這種方式稱為文法糖。

枚舉類VS普通類

枚舉類就是類,按照這個邏輯來測試下它和普通類的差别

添加構造函數:

Java 枚舉詳解

紅色行提示編譯錯誤“找不到這樣的構造函數”,常量聲明處添加參數,如下代碼正确:

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普通類的不同點:

  1. 不可以顯示聲明繼承關系;
  2. 常量聲明,等價調用構造方法;
  3. 允許有多個構造方法,但修飾符有且僅是

    private

  4. 其他地方同類一般無二,可以添加自定添加(方法、字段,抽象成員), 實作接口;

枚舉類VS匿名類

看看以下如此誇張的寫法,也能編譯成功:

Java 枚舉詳解

觀察生成的位元組碼檔案:

Java 枚舉詳解

把枚舉常量聲明的地方替換成構造方法調用

new ArticleStateEnum(v1, m1)

,這不就是匿名類的聲明嗎!

現在向枚舉類内添加抽象方放,看看結果:

Java 枚舉詳解

編譯報錯“提示有抽象方法未實作”,驗證了前面的猜想,這是匿名類的實作,不過不可以顯式的使用使用匿名實作枚舉類的方式!

常用方法

詳細參見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語言的枚舉的輕量:

  1. 枚舉常量不可以像C語言一樣使用移位運算。
  2. 枚舉常量和外部互動麻煩,比如:
    • 在mybatis中儲存帶有枚舉字段的實體時,需要你編寫轉化器(除非按照預設的聲明順序);
                 
    • 轉化為JSON資料時;
                 
    • Spring MVC對請求參數封裝時,需要自定義轉換器;
  3. 枚舉常量無法繼承,意味着相似的枚舉類之間無法繼承,導緻産生備援代碼;

建議無特殊情況還是使用枚舉常量,畢竟軟體的正确性是最重要的