Java 枚舉詳解

爲何要用枚舉

在博客系統中,一篇文章有且可能有這幾種狀態, 數據庫中article文章表中state字段存儲數值,表示其狀態:java

  • 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前經常使用的解決方式:mybatis

/**
 * 定義了文章的狀態
 */
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接口:this

/**
 * dao接口修改文章狀態方法
 * @param articleId 文章ID
 * @param state 狀態
 */
int updateState(int id, ArticleStateEnum state);

接着Service調用:code

// 修改文章狀態爲發表
articleDao.updateState(id, ArticleStateEnum.PUBLISHED);

以上代碼語義清晰,如今傳遞參數的類型爲ArticleStateEnum, 解決了以前描述的兩個問題

枚舉的本質

使用JDK附帶工具javap反編譯生枚舉類字節碼, 注javap反編譯只會獲得public成員:

枚舉類本質

看反編譯的獲得的代碼:

  1. class聲明,意味着枚舉的本質也是類;
  2. 父類聲明爲java.lang.Enum<>, 意味着枚舉類不容許顯式使用extends聲明父類,包括聲明爲java.lang.Enum<>也會報錯;
  3. 枚舉常量,經過public static final修飾符實現,ArticlestateEnum類型聲明,意味着全部枚舉常量本質是當前枚舉類的對象;
  4. 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普通類的不一樣點:

  1. 不能夠顯示聲明繼承關係;
  2. 常量聲明,等價調用構造方法;
  3. 容許有多個構造方法,但修飾符有且僅是private;
  4. 其餘地方同類通常無二,能夠添加自定添加(方法、字段,抽象成員), 實現接口;

枚舉類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語言的枚舉的輕量:

  1. 枚舉常量不能夠像C語言同樣使用移位運算。
  2. 枚舉常量和外部交互麻煩,好比:
    • 在mybatis中保存帶有枚舉字段的實體時,須要你編寫轉化器(除非按照默認的聲明順序);
    • 轉化爲JSON數據時;
    • Spring MVC對請求參數封裝時,須要自定義轉換器;
  3. 枚舉常量沒法繼承,意味着類似的枚舉類之間沒法繼承,致使產生冗餘代碼;

建議無特殊狀況仍是使用枚舉常量,畢竟軟件的正確性是最重要的

相關文章
相關標籤/搜索